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 }