github.com/aretext/aretext@v1.3.0/syntax/parser/combinators_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 func TestMaybeBefore(t *testing.T) { 14 // Parse consecutive numerals as numbers. 15 firstParseFunc := func(iter TrackingRuneIter, state State) Result { 16 var n uint64 17 for { 18 r, err := iter.NextRune() 19 if err != nil || r < '0' || r > '9' { 20 break 21 } 22 n++ 23 } 24 return Result{ 25 NumConsumed: n, 26 ComputedTokens: []ComputedToken{ 27 { 28 Length: n, 29 Role: TokenRoleNumber, 30 }, 31 }, 32 NextState: state, 33 } 34 } 35 36 // Parse alpha characters as keywords. 37 secondParseFunc := func(iter TrackingRuneIter, state State) Result { 38 var n uint64 39 for { 40 r, err := iter.NextRune() 41 if err != nil || r < 'A' || r > 'z' { 42 break 43 } 44 n++ 45 } 46 return Result{ 47 NumConsumed: n, 48 ComputedTokens: []ComputedToken{ 49 { 50 Length: n, 51 Role: TokenRoleKeyword, 52 }, 53 }, 54 NextState: state, 55 } 56 } 57 58 // Alpha characters, optionally prefixed with spaces. 59 combinedParseFunc := Func(firstParseFunc).MaybeBefore(Func(secondParseFunc)) 60 61 testCases := []struct { 62 name string 63 text string 64 expected []Token 65 }{ 66 { 67 name: "only second parse func", 68 text: "abc", 69 expected: []Token{ 70 {StartPos: 0, EndPos: 3, Role: TokenRoleKeyword}, 71 }, 72 }, 73 { 74 name: "first and second parse func", 75 text: "1234abc", 76 expected: []Token{ 77 {StartPos: 0, EndPos: 4, Role: TokenRoleNumber}, 78 {StartPos: 4, EndPos: 7, Role: TokenRoleKeyword}, 79 }, 80 }, 81 { 82 name: "only first parse func", 83 text: "1234", 84 expected: nil, 85 }, 86 } 87 88 for _, tc := range testCases { 89 t.Run(tc.name, func(t *testing.T) { 90 tree, err := text.NewTreeFromString(tc.text) 91 require.NoError(t, err) 92 93 p := New(combinedParseFunc) 94 p.ParseAll(tree) 95 tokens := p.TokensIntersectingRange(0, math.MaxUint64) 96 assert.Equal(t, tc.expected, tokens) 97 }) 98 } 99 100 } 101 102 func TestThenCombinatorShiftTokens(t *testing.T) { 103 // Parse up to ":" as a keyword. 104 firstParseFunc := func(iter TrackingRuneIter, state State) Result { 105 var n uint64 106 for { 107 r, err := iter.NextRune() 108 if err != nil || r == ':' { 109 break 110 } 111 n++ 112 } 113 return Result{ 114 NumConsumed: n, 115 NextState: state, 116 ComputedTokens: []ComputedToken{ 117 { 118 Length: n, 119 Role: TokenRoleKeyword, 120 }, 121 }, 122 } 123 } 124 125 // Parse rest of the string as a number. 126 secondParseFunc := func(iter TrackingRuneIter, state State) Result { 127 var n uint64 128 for { 129 _, err := iter.NextRune() 130 if err != nil { 131 break 132 } 133 n++ 134 } 135 return Result{ 136 NumConsumed: n, 137 NextState: state, 138 ComputedTokens: []ComputedToken{ 139 { 140 Length: n, 141 Role: TokenRoleNumber, 142 }, 143 }, 144 } 145 } 146 147 tree, err := text.NewTreeFromString("abc:123") 148 require.NoError(t, err) 149 150 combinedParseFunc := Func(firstParseFunc).Then(Func(secondParseFunc)) 151 p := New(combinedParseFunc) 152 p.ParseAll(tree) 153 tokens := p.TokensIntersectingRange(0, math.MaxUint64) 154 expectedTokens := []Token{ 155 {StartPos: 0, EndPos: 3, Role: TokenRoleKeyword}, 156 {StartPos: 3, EndPos: 7, Role: TokenRoleNumber}, 157 } 158 assert.Equal(t, expectedTokens, tokens) 159 } 160 161 func TestMapWithInput(t *testing.T) { 162 // Parse func that consumes up to three runes in the input without producing any tokens. 163 parseFunc := func(iter TrackingRuneIter, state State) Result { 164 var n uint64 165 for n < 3 { 166 _, err := iter.NextRune() 167 if err != nil { 168 break 169 } 170 n++ 171 } 172 return Result{ 173 NumConsumed: n, 174 NextState: state, 175 } 176 } 177 178 // MapFn that produces a single token with length of runes read from the iterator. 179 // This validates that the iterator returns only runes consumed by the parse func. 180 mapFn := func(result Result, iter TrackingRuneIter, state State) Result { 181 var n uint64 182 for { 183 _, err := iter.NextRune() 184 if err != nil { 185 break 186 } 187 n++ 188 } 189 190 result.ComputedTokens = append(result.ComputedTokens, ComputedToken{ 191 Offset: 0, 192 Length: n, 193 Role: TokenRoleNumber, 194 }) 195 return result 196 } 197 198 tree, err := text.NewTreeFromString("abc123") 199 require.NoError(t, err) 200 201 combinedParseFunc := Func(parseFunc).MapWithInput(mapFn) 202 p := New(combinedParseFunc) 203 p.ParseAll(tree) 204 tokens := p.TokensIntersectingRange(0, math.MaxUint64) 205 expectedTokens := []Token{ 206 {StartPos: 0, EndPos: 3, Role: TokenRoleNumber}, 207 {StartPos: 3, EndPos: 6, Role: TokenRoleNumber}, 208 } 209 assert.Equal(t, expectedTokens, tokens) 210 }