github.com/aretext/aretext@v1.3.0/syntax/parser/parser_test.go (about)

     1  package parser
     2  
     3  import (
     4  	"math"
     5  	"testing"
     6  
     7  	"github.com/stretchr/testify/assert"
     8  	"github.com/stretchr/testify/require"
     9  
    10  	"github.com/aretext/aretext/text"
    11  )
    12  
    13  // simpleParseFunc recognizes strings prefixed and suffixed with a double-quote.
    14  func simpleParseFunc(iter TrackingRuneIter, state State) Result {
    15  	// Consume the first character in the text.
    16  	r, err := iter.NextRune()
    17  	if err != nil {
    18  		return FailedResult
    19  	}
    20  	n := uint64(1)
    21  
    22  	if r == '"' {
    23  		// Text starts with a double-quote, so search for a matching double-quote.
    24  		for {
    25  			r, err = iter.NextRune()
    26  			if err != nil {
    27  				// No double-quote found before EOF, so consume without producing any tokens.
    28  				return Result{NumConsumed: n, NextState: state}
    29  			} else if r == '"' {
    30  				// Found matching double-quote at end, so produce a string token.
    31  				token := ComputedToken{
    32  					Length: n + 1,
    33  					Role:   TokenRoleString,
    34  				}
    35  				return Result{
    36  					NumConsumed:    token.Length,
    37  					ComputedTokens: []ComputedToken{token},
    38  					NextState:      state,
    39  				}
    40  			}
    41  			n++
    42  		}
    43  	} else {
    44  		// Text does not start with a double-quote, so consume up to the start
    45  		// of the next double-quote or EOF.
    46  		for {
    47  			r, err = iter.NextRune()
    48  			if err != nil || r == '"' {
    49  				return Result{NumConsumed: n, NextState: state}
    50  			}
    51  			n++
    52  		}
    53  	}
    54  }
    55  
    56  func TestParseAll(t *testing.T) {
    57  	testCases := []struct {
    58  		name           string
    59  		text           string
    60  		expectedTokens []Token
    61  	}{
    62  		{
    63  			name:           "empty",
    64  			text:           "",
    65  			expectedTokens: nil,
    66  		},
    67  		{
    68  			name: "recognize single token",
    69  			text: `"test"`,
    70  			expectedTokens: []Token{
    71  				{StartPos: 0, EndPos: 6, Role: TokenRoleString},
    72  			},
    73  		},
    74  		{
    75  			name: "recognize multiple tokens",
    76  			text: `"foo""bar"`,
    77  			expectedTokens: []Token{
    78  				{StartPos: 0, EndPos: 5, Role: TokenRoleString},
    79  				{StartPos: 5, EndPos: 10, Role: TokenRoleString},
    80  			},
    81  		},
    82  		{
    83  			name:           "consume all without recognizing tokens",
    84  			text:           `foobar`,
    85  			expectedTokens: nil,
    86  		},
    87  		{
    88  			name: "token in middle",
    89  			text: `    "test"    `,
    90  			expectedTokens: []Token{
    91  				{StartPos: 4, EndPos: 10, Role: TokenRoleString},
    92  			},
    93  		},
    94  	}
    95  
    96  	for _, tc := range testCases {
    97  		t.Run(tc.name, func(t *testing.T) {
    98  			tree, err := text.NewTreeFromString(tc.text)
    99  			require.NoError(t, err)
   100  			p := New(simpleParseFunc)
   101  			p.ParseAll(tree)
   102  			tokens := p.TokensIntersectingRange(0, math.MaxUint64)
   103  			assert.Equal(t, tc.expectedTokens, tokens)
   104  		})
   105  	}
   106  }
   107  
   108  func TestRecoverFromFailure(t *testing.T) {
   109  	failingParseFunc := func(iter TrackingRuneIter, state State) Result {
   110  		return FailedResult
   111  	}
   112  	tree, err := text.NewTreeFromString("abcd")
   113  	require.NoError(t, err)
   114  	p := New(failingParseFunc)
   115  	p.ParseAll(tree)
   116  	assert.Equal(t, uint64(4), p.lastComputation.ConsumedLength())
   117  	tokens := p.TokensIntersectingRange(0, math.MaxUint64)
   118  	assert.Equal(t, 0, len(tokens))
   119  }
   120  
   121  func TestReparseAfterEditInsertion(t *testing.T) {
   122  	testCases := []struct {
   123  		name           string
   124  		text           string
   125  		editPos        uint64
   126  		insertString   string
   127  		expectedTokens []Token
   128  	}{
   129  		{
   130  			name:           "empty, insert empty",
   131  			text:           "",
   132  			editPos:        0,
   133  			insertString:   "",
   134  			expectedTokens: nil,
   135  		},
   136  		{
   137  			name:         "empty, insert token",
   138  			text:         "",
   139  			editPos:      0,
   140  			insertString: `"test"`,
   141  			expectedTokens: []Token{
   142  				{StartPos: 0, EndPos: 6, Role: TokenRoleString},
   143  			},
   144  		},
   145  		{
   146  			name:           "empty, insert text but no tokens",
   147  			text:           "",
   148  			editPos:        0,
   149  			insertString:   `test`,
   150  			expectedTokens: nil,
   151  		},
   152  		{
   153  			name:         "change tokens",
   154  			text:         `"this is a test"`,
   155  			editPos:      5,
   156  			insertString: `"`,
   157  			expectedTokens: []Token{
   158  				{StartPos: 0, EndPos: 6, Role: TokenRoleString},
   159  			},
   160  		},
   161  		{
   162  			name:         "affect multiple tokens",
   163  			text:         `"this is a test"`,
   164  			editPos:      5,
   165  			insertString: `" "`,
   166  			expectedTokens: []Token{
   167  				{StartPos: 0, EndPos: 6, Role: TokenRoleString},
   168  				{StartPos: 7, EndPos: 19, Role: TokenRoleString},
   169  			},
   170  		},
   171  		{
   172  			name:         "affect some tokens but not others",
   173  			text:         `"foo" "bar" "baz"`,
   174  			editPos:      7,
   175  			insertString: `x`,
   176  			expectedTokens: []Token{
   177  				{StartPos: 0, EndPos: 5, Role: TokenRoleString},
   178  				{StartPos: 6, EndPos: 12, Role: TokenRoleString},
   179  				{StartPos: 13, EndPos: 18, Role: TokenRoleString},
   180  			},
   181  		},
   182  	}
   183  
   184  	for _, tc := range testCases {
   185  		t.Run(tc.name, func(t *testing.T) {
   186  			tree, err := text.NewTreeFromString(tc.text)
   187  			require.NoError(t, err)
   188  			p := New(simpleParseFunc)
   189  			p.ParseAll(tree)
   190  
   191  			var n uint64
   192  			for _, r := range tc.insertString {
   193  				err = tree.InsertAtPosition(tc.editPos+n, r)
   194  				require.NoError(t, err)
   195  				n++
   196  			}
   197  
   198  			edit := NewInsertEdit(tc.editPos, n)
   199  			p.ReparseAfterEdit(tree, edit)
   200  			tokens := p.TokensIntersectingRange(0, math.MaxUint64)
   201  			assert.Equal(t, tc.expectedTokens, tokens)
   202  		})
   203  	}
   204  }
   205  
   206  func TestReparseAfterEditDeletion(t *testing.T) {
   207  	testCases := []struct {
   208  		name           string
   209  		text           string
   210  		editPos        uint64
   211  		numDeleted     uint64
   212  		expectedTokens []Token
   213  	}{
   214  		{
   215  			name:           "no tokens, delete a few characters",
   216  			text:           "abcdefghijk",
   217  			editPos:        2,
   218  			numDeleted:     3,
   219  			expectedTokens: nil,
   220  		},
   221  		{
   222  			name:       "delete affects tokens",
   223  			text:       `"foo"bar"`,
   224  			editPos:    4,
   225  			numDeleted: 1,
   226  			expectedTokens: []Token{
   227  				{StartPos: 0, EndPos: 8, Role: TokenRoleString},
   228  			},
   229  		},
   230  		{
   231  			name:       "delete changes length of existing token",
   232  			text:       `"foobar"`,
   233  			editPos:    4,
   234  			numDeleted: 2,
   235  			expectedTokens: []Token{
   236  				{StartPos: 0, EndPos: 6, Role: TokenRoleString},
   237  			},
   238  		},
   239  		{
   240  			name:       "delete affects multiple tokens",
   241  			text:       `"foo" "bar" "baz"`,
   242  			editPos:    4,
   243  			numDeleted: 1,
   244  			expectedTokens: []Token{
   245  				{StartPos: 0, EndPos: 6, Role: TokenRoleString},
   246  				{StartPos: 9, EndPos: 12, Role: TokenRoleString},
   247  			},
   248  		},
   249  		{
   250  			name:       "delete affects some tokens but not others",
   251  			text:       `"foo" "bar" "baz"`,
   252  			editPos:    8,
   253  			numDeleted: 1,
   254  			expectedTokens: []Token{
   255  				{StartPos: 0, EndPos: 5, Role: TokenRoleString},
   256  				{StartPos: 6, EndPos: 10, Role: TokenRoleString},
   257  				{StartPos: 11, EndPos: 16, Role: TokenRoleString},
   258  			},
   259  		},
   260  	}
   261  
   262  	for _, tc := range testCases {
   263  		t.Run(tc.name, func(t *testing.T) {
   264  			tree, err := text.NewTreeFromString(tc.text)
   265  			require.NoError(t, err)
   266  			p := New(simpleParseFunc)
   267  			p.ParseAll(tree)
   268  
   269  			for i := uint64(0); i < tc.numDeleted; i++ {
   270  				tree.DeleteAtPosition(tc.editPos + i)
   271  			}
   272  
   273  			edit := NewDeleteEdit(tc.editPos, tc.numDeleted)
   274  			p.ReparseAfterEdit(tree, edit)
   275  			tokens := p.TokensIntersectingRange(0, math.MaxUint64)
   276  			assert.Equal(t, tc.expectedTokens, tokens)
   277  		})
   278  	}
   279  }
   280  
   281  func TestReparseIndividualInsertionsAtEndOfDocument(t *testing.T) {
   282  	tree := text.NewTree()
   283  	p := New(simpleParseFunc)
   284  	p.ParseAll(tree)
   285  	text := `"test"`
   286  	for i, r := range text {
   287  		pos := uint64(i)
   288  		err := tree.InsertAtPosition(pos, r)
   289  		require.NoError(t, err)
   290  		edit := NewInsertEdit(pos, 1)
   291  		p.ReparseAfterEdit(tree, edit)
   292  	}
   293  	tokens := p.TokensIntersectingRange(0, math.MaxUint64)
   294  	expectedTokens := []Token{
   295  		{StartPos: 0, EndPos: uint64(len(text)), Role: TokenRoleString},
   296  	}
   297  	assert.Equal(t, expectedTokens, tokens)
   298  }