298 pos += 1 |
298 pos += 1 |
299 |
299 |
300 if quote: |
300 if quote: |
301 raise error.ParseError(_(b"unterminated string"), start) |
301 raise error.ParseError(_(b"unterminated string"), start) |
302 except error.ParseError as inst: |
302 except error.ParseError as inst: |
303 if len(inst.args) > 1: # has location |
303 _addparseerrorhint(inst, tmpl) |
304 loc = inst.args[1] |
|
305 # Offset the caret location by the number of newlines before the |
|
306 # location of the error, since we will replace one-char newlines |
|
307 # with the two-char literal r'\n'. |
|
308 offset = tmpl[:loc].count(b'\n') |
|
309 tmpl = tmpl.replace(b'\n', br'\n') |
|
310 # We want the caret to point to the place in the template that |
|
311 # failed to parse, but in a hint we get a open paren at the |
|
312 # start. Therefore, we print "loc + 1" spaces (instead of "loc") |
|
313 # to line up the caret with the location of the error. |
|
314 inst.hint = ( |
|
315 tmpl + b'\n' + b' ' * (loc + 1 + offset) + b'^ ' + _(b'here') |
|
316 ) |
|
317 raise |
304 raise |
318 yield (b'end', None, pos) |
305 yield (b'end', None, pos) |
|
306 |
|
307 |
|
308 def _addparseerrorhint(inst, tmpl): |
|
309 if len(inst.args) <= 1: |
|
310 return # no location |
|
311 loc = inst.args[1] |
|
312 # Offset the caret location by the number of newlines before the |
|
313 # location of the error, since we will replace one-char newlines |
|
314 # with the two-char literal r'\n'. |
|
315 offset = tmpl[:loc].count(b'\n') |
|
316 tmpl = tmpl.replace(b'\n', br'\n') |
|
317 # We want the caret to point to the place in the template that |
|
318 # failed to parse, but in a hint we get a open paren at the |
|
319 # start. Therefore, we print "loc + 1" spaces (instead of "loc") |
|
320 # to line up the caret with the location of the error. |
|
321 inst.hint = tmpl + b'\n' + b' ' * (loc + 1 + offset) + b'^ ' + _(b'here') |
319 |
322 |
320 |
323 |
321 def _unnesttemplatelist(tree): |
324 def _unnesttemplatelist(tree): |
322 """Expand list of templates to node tuple |
325 """Expand list of templates to node tuple |
323 |
326 |
358 parsed, pos = _parsetemplate(tmpl, 0, len(tmpl)) |
361 parsed, pos = _parsetemplate(tmpl, 0, len(tmpl)) |
359 assert pos == len(tmpl), b'unquoted template should be consumed' |
362 assert pos == len(tmpl), b'unquoted template should be consumed' |
360 return _unnesttemplatelist((b'template', parsed)) |
363 return _unnesttemplatelist((b'template', parsed)) |
361 |
364 |
362 |
365 |
363 def _parseexpr(expr): |
366 def parseexpr(expr): |
364 """Parse a template expression into tree |
367 """Parse a template expression into tree |
365 |
368 |
366 >>> _parseexpr(b'"foo"') |
369 >>> parseexpr(b'"foo"') |
367 ('string', 'foo') |
370 ('string', 'foo') |
368 >>> _parseexpr(b'foo(bar)') |
371 >>> parseexpr(b'foo(bar)') |
369 ('func', ('symbol', 'foo'), ('symbol', 'bar')) |
372 ('func', ('symbol', 'foo'), ('symbol', 'bar')) |
370 >>> _parseexpr(b'foo(') |
373 >>> parseexpr(b'foo(') |
371 Traceback (most recent call last): |
374 Traceback (most recent call last): |
372 ... |
375 ... |
373 ParseError: ('not a prefix: end', 4) |
376 ParseError: ('not a prefix: end', 4) |
374 >>> _parseexpr(b'"foo" "bar"') |
377 >>> parseexpr(b'"foo" "bar"') |
375 Traceback (most recent call last): |
378 Traceback (most recent call last): |
376 ... |
379 ... |
377 ParseError: ('invalid token', 7) |
380 ParseError: ('invalid token', 7) |
378 """ |
381 """ |
|
382 try: |
|
383 return _parseexpr(expr) |
|
384 except error.ParseError as inst: |
|
385 _addparseerrorhint(inst, expr) |
|
386 raise |
|
387 |
|
388 |
|
389 def _parseexpr(expr): |
379 p = parser.parser(elements) |
390 p = parser.parser(elements) |
380 tree, pos = p.parse(tokenize(expr, 0, len(expr))) |
391 tree, pos = p.parse(tokenize(expr, 0, len(expr))) |
381 if pos != len(expr): |
392 if pos != len(expr): |
382 raise error.ParseError(_(b'invalid token'), pos) |
393 raise error.ParseError(_(b'invalid token'), pos) |
383 return _unnesttemplatelist(tree) |
394 return _unnesttemplatelist(tree) |