mirror of
https://github.com/python/cpython.git
synced 2026-10-28 18:35:21 +08:00
621 lines
21 KiB
C
621 lines
21 KiB
C
#include "Python.h"
|
|
#include "pycore_token.h"
|
|
#include "errcode.h"
|
|
|
|
#include "lexer_internal.h"
|
|
#include "../tokenizer/helpers.h"
|
|
|
|
#define MAKE_TOKEN(token_type) _PyLexer_token_setup(tok, token, token_type, p_start, p_end)
|
|
|
|
static int
|
|
string_error_token(struct tok_state *tok, struct token *token,
|
|
_PyTok_Off start, _PyTok_Loc location)
|
|
{
|
|
tok->diagnostic = (_PyTokenizer_Diagnostic){
|
|
.location = {location.lineno, location.byte_col + 1},
|
|
.text_span = {start - location.byte_col, tok->inp},
|
|
};
|
|
int type = _PyLexer_token_setup(tok, token, ERRORTOKEN, -1, -1);
|
|
token->start_loc = location;
|
|
token->end_loc = (_PyTok_Loc){location.lineno, -1};
|
|
return type;
|
|
}
|
|
|
|
static int
|
|
incomplete_string_token(struct tok_state *tok, struct token *token,
|
|
int type, _PyTok_Off start, _PyTok_Loc location)
|
|
{
|
|
string_error_token(tok, token, start, location);
|
|
tok->done = E_INCOMPLETE;
|
|
token->is_incomplete = 1;
|
|
return type;
|
|
}
|
|
|
|
int
|
|
_PyLexer_record_ftstring_comment(struct tok_state *tok, ftstring_state *state,
|
|
_PyTok_Off start, _PyTok_Off end)
|
|
{
|
|
assert(state == _PyLexer_CurrentFTString(tok) && state->mode == FTSTRING_MODE_EXPRESSION);
|
|
if (state->expr_span.end >= 0) {
|
|
return 0;
|
|
}
|
|
assert(state->expr_span.start >= 0);
|
|
tokenizer_comments *comments = state->comments;
|
|
if (comments == NULL || comments->count == comments->capacity) {
|
|
Py_ssize_t max_capacity = (PY_SSIZE_T_MAX -
|
|
(Py_ssize_t)sizeof(*comments)) /
|
|
(Py_ssize_t)sizeof(*comments->spans);
|
|
if (comments != NULL && comments->capacity > max_capacity / 2) {
|
|
PyErr_NoMemory();
|
|
return -1;
|
|
}
|
|
Py_ssize_t capacity = comments == NULL ? 4 : comments->capacity * 2;
|
|
size_t size = sizeof(*comments) +
|
|
(size_t)capacity * sizeof(*comments->spans);
|
|
tokenizer_comments *resized = PyMem_Realloc(comments, size);
|
|
if (resized == NULL) {
|
|
PyErr_NoMemory();
|
|
return -1;
|
|
}
|
|
comments = resized;
|
|
if (state->comments == NULL) {
|
|
comments->count = 0;
|
|
}
|
|
comments->capacity = capacity;
|
|
state->comments = comments;
|
|
}
|
|
comments->spans[comments->count++] =
|
|
(_PyTok_Span){start, end};
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
finish_ftstring_expr(struct tok_state *tok, ftstring_state *state,
|
|
struct token *token)
|
|
{
|
|
assert(token != NULL && state == _PyLexer_CurrentFTString(tok));
|
|
assert(state->mode == FTSTRING_MODE_EXPRESSION && tok->start >= 0);
|
|
|
|
if (state->expr_span.end >= 0) {
|
|
return 0;
|
|
}
|
|
assert(state->expr_span.start >= 0);
|
|
state->expr_span.end = tok->start;
|
|
int tstring_interpolation = _PyLexer_IsTString(state->kind) &&
|
|
state->replacement_depth == 1;
|
|
if (!(state->debug_expr || tstring_interpolation) || token->metadata) {
|
|
return 0;
|
|
}
|
|
tokenizer_comments *comments = state->comments;
|
|
PyObject *res;
|
|
if (comments != NULL && comments->count > 0) {
|
|
Py_ssize_t stripped_size =
|
|
state->expr_span.end - state->expr_span.start;
|
|
Py_ssize_t comment_count = 0;
|
|
for (Py_ssize_t i = 0; i < comments->count; i++) {
|
|
_PyTok_Span comment = comments->spans[i];
|
|
assert(_PyTok_SpanIsValid(comment));
|
|
assert(comment.start >= (i == 0 ? state->expr_span.start
|
|
: comments->spans[i - 1].end));
|
|
if (comment.start >= state->expr_span.end) {
|
|
break;
|
|
}
|
|
assert(comment.end <= state->expr_span.end);
|
|
stripped_size -= comment.end - comment.start;
|
|
comment_count++;
|
|
}
|
|
char *stripped = PyMem_Malloc((size_t)stripped_size);
|
|
if (stripped == NULL) {
|
|
PyErr_NoMemory();
|
|
return -1;
|
|
}
|
|
_PyTok_Span kept = {state->expr_span.start, state->expr_span.start};
|
|
Py_ssize_t stripped_len = 0;
|
|
for (Py_ssize_t i = 0; i < comment_count; i++) {
|
|
kept.end = comments->spans[i].start;
|
|
Py_ssize_t length;
|
|
const char *text = _PyTok_SourceSpanView(
|
|
&tok->source, kept, &length);
|
|
memcpy(stripped + stripped_len, text, (size_t)length);
|
|
stripped_len += length;
|
|
kept.start = comments->spans[i].end;
|
|
}
|
|
kept.end = state->expr_span.end;
|
|
Py_ssize_t length;
|
|
const char *text = _PyTok_SourceSpanView(&tok->source, kept, &length);
|
|
memcpy(stripped + stripped_len, text, (size_t)length);
|
|
res = PyUnicode_DecodeUTF8(stripped, stripped_size, NULL);
|
|
PyMem_Free(stripped);
|
|
}
|
|
else {
|
|
Py_ssize_t expr_len;
|
|
const char *expr = _PyTok_SourceSpanView(
|
|
&tok->source, state->expr_span, &expr_len);
|
|
res = PyUnicode_DecodeUTF8(expr, expr_len, NULL);
|
|
}
|
|
|
|
if (!res) {
|
|
return -1;
|
|
}
|
|
token->metadata = res;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
begin_ftstring_expr(struct tok_state *tok, ftstring_state *state,
|
|
_PyTok_Off expr_start)
|
|
{
|
|
assert(state->mode != FTSTRING_MODE_EXPRESSION);
|
|
state->expr_span = (_PyTok_Span){expr_start, -1};
|
|
if (state->comments != NULL) {
|
|
state->comments->count = 0;
|
|
}
|
|
if (state->replacement_depth >= MAX_EXPR_NESTING) {
|
|
_PyTokenizer_syntaxerror(
|
|
tok, "%c-string: expressions nested too deeply",
|
|
_PyLexer_StringPrefix(state->kind));
|
|
return -1;
|
|
}
|
|
state->replacement_depth++;
|
|
state->mode = FTSTRING_MODE_EXPRESSION;
|
|
state->debug_expr = 0;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
_PyLexer_ftstring_punctuation(struct tok_state *tok, ftstring_state *state,
|
|
struct token *token, int c)
|
|
{
|
|
assert(state->mode == FTSTRING_MODE_EXPRESSION);
|
|
assert(c == ':' || c == '}' || c == '!');
|
|
if (_PyLexer_FTStringBracketDepth(tok, state) != state->replacement_depth) {
|
|
return 0;
|
|
}
|
|
if (c == '!') {
|
|
int next = tok_nextc(tok);
|
|
tok_backup(tok, next);
|
|
if (next == '=') {
|
|
return 0;
|
|
}
|
|
}
|
|
if (finish_ftstring_expr(tok, state, token) < 0) {
|
|
return -1;
|
|
}
|
|
if (c == ':') {
|
|
state->mode = FTSTRING_MODE_FORMAT_SPEC;
|
|
return COLON;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
_PyLexer_close_ftstring_expr(struct tok_state *tok, ftstring_state *state,
|
|
int c)
|
|
{
|
|
assert(state->mode == FTSTRING_MODE_EXPRESSION);
|
|
assert(c == ')' || c == ']' || c == '}');
|
|
int depth = _PyLexer_FTStringBracketDepth(tok, state);
|
|
if (depth < 0) {
|
|
_PyTokenizer_syntaxerror(tok, "%c-string: unmatched '%c'",
|
|
_PyLexer_StringPrefix(state->kind), c);
|
|
return -1;
|
|
}
|
|
if (c == '}' && depth == state->replacement_depth - 1) {
|
|
state->replacement_depth--;
|
|
state->mode = FTSTRING_MODE_MIDDLE;
|
|
state->debug_expr = 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
_PyLexer_mark_ftstring_debug(struct tok_state *tok, ftstring_state *state)
|
|
{
|
|
assert(state->mode == FTSTRING_MODE_EXPRESSION);
|
|
if (_PyLexer_FTStringBracketDepth(tok, state) == state->replacement_depth) {
|
|
state->debug_expr = 1;
|
|
}
|
|
}
|
|
|
|
int
|
|
_PyLexer_check_string_prefixes(struct tok_state *tok,
|
|
int saw_b, int saw_r, int saw_u,
|
|
int saw_f, int saw_t) {
|
|
// Supported: rb, rf, rt (in any order)
|
|
// Unsupported: ub, ur, uf, ut, bf, bt, ft (in any order)
|
|
|
|
#define RETURN_SYNTAX_ERROR(PREFIX1, PREFIX2) \
|
|
do { \
|
|
(void)_PyTokenizer_syntaxerror_known_range( \
|
|
tok, (int)(tok->start + 1 - tok->line_start), \
|
|
(int)(tok->cur - tok->line_start), \
|
|
"'" PREFIX1 "' and '" PREFIX2 "' prefixes are incompatible"); \
|
|
return -1; \
|
|
} while (0)
|
|
|
|
if (saw_u && saw_b) {
|
|
RETURN_SYNTAX_ERROR("u", "b");
|
|
}
|
|
if (saw_u && saw_r) {
|
|
RETURN_SYNTAX_ERROR("u", "r");
|
|
}
|
|
if (saw_u && saw_f) {
|
|
RETURN_SYNTAX_ERROR("u", "f");
|
|
}
|
|
if (saw_u && saw_t) {
|
|
RETURN_SYNTAX_ERROR("u", "t");
|
|
}
|
|
|
|
if (saw_b && saw_f) {
|
|
RETURN_SYNTAX_ERROR("b", "f");
|
|
}
|
|
if (saw_b && saw_t) {
|
|
RETURN_SYNTAX_ERROR("b", "t");
|
|
}
|
|
|
|
if (saw_f && saw_t) {
|
|
RETURN_SYNTAX_ERROR("f", "t");
|
|
}
|
|
|
|
#undef RETURN_SYNTAX_ERROR
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
_PyLexer_scan_fstring_start(struct tok_state *tok, struct token *token,
|
|
int c, ftstring_kind kind)
|
|
{
|
|
_PyTok_Off p_start = -1;
|
|
_PyTok_Off p_end = -1;
|
|
|
|
int quote = c;
|
|
int quote_size = 1; /* 1 or 3 */
|
|
|
|
/* Find the quote size and start of string */
|
|
int after_quote = tok_nextc(tok);
|
|
if (after_quote == quote) {
|
|
int after_after_quote = tok_nextc(tok);
|
|
if (after_after_quote == quote) {
|
|
quote_size = 3;
|
|
}
|
|
else {
|
|
// TODO: Check this
|
|
tok_backup(tok, after_after_quote);
|
|
tok_backup(tok, after_quote);
|
|
}
|
|
}
|
|
if (after_quote != quote) {
|
|
tok_backup(tok, after_quote);
|
|
}
|
|
|
|
p_start = tok->start;
|
|
p_end = tok->cur;
|
|
ftstring_state *state = _PyLexer_PushFTString(tok);
|
|
if (state == NULL) {
|
|
return MAKE_TOKEN(ERRORTOKEN);
|
|
}
|
|
state->mode = FTSTRING_MODE_MIDDLE;
|
|
state->quote = quote;
|
|
state->quote_size = quote_size;
|
|
state->paren_level = tok->level;
|
|
state->start = tok->start;
|
|
state->start_loc = tok->start_loc;
|
|
state->expr_span = (_PyTok_Span){-1, -1};
|
|
|
|
state->kind = kind;
|
|
return _PyLexer_IsTString(kind)
|
|
? MAKE_TOKEN(TSTRING_START) : MAKE_TOKEN(FSTRING_START);
|
|
}
|
|
|
|
int
|
|
_PyLexer_scan_string(struct tok_state *tok, struct token *token, int c)
|
|
{
|
|
_PyTok_Off p_start = -1;
|
|
_PyTok_Off p_end = -1;
|
|
|
|
int quote = c;
|
|
int quote_size = 1; /* 1 or 3 */
|
|
int end_quote_size = 0;
|
|
int has_escaped_quote = 0;
|
|
|
|
/* Find the quote size and start of string */
|
|
c = tok_nextc(tok);
|
|
if (c == quote) {
|
|
c = tok_nextc(tok);
|
|
if (c == quote) {
|
|
quote_size = 3;
|
|
}
|
|
else {
|
|
end_quote_size = 1; /* empty string found */
|
|
}
|
|
}
|
|
if (c != quote) {
|
|
tok_backup(tok, c);
|
|
}
|
|
|
|
/* Get rest of string */
|
|
while (end_quote_size != quote_size) {
|
|
c = tok_nextc(tok);
|
|
if (tok->done == E_ERROR) {
|
|
return MAKE_TOKEN(ERRORTOKEN);
|
|
}
|
|
if (tok->done == E_DECODE) {
|
|
break;
|
|
}
|
|
if (c == EOF || (quote_size == 1 && c == '\n')) {
|
|
if (tok_failed(tok)) {
|
|
return MAKE_TOKEN(ERRORTOKEN);
|
|
}
|
|
if (c == EOF && tok->allow_incomplete_input) {
|
|
return incomplete_string_token(tok, token, STRING,
|
|
tok->start, tok->start_loc);
|
|
}
|
|
int end_lineno = tok->lineno;
|
|
_PyTok_Loc location = tok->start_loc;
|
|
const char *line = _PyTok_SourcePointer(
|
|
&tok->source, tok->start - location.byte_col);
|
|
Py_ssize_t cursor_offset = (Py_ssize_t)location.byte_col + 1;
|
|
|
|
const ftstring_state *state = _PyLexer_CurrentFTString(tok);
|
|
if (state != NULL) {
|
|
/* A matching quote may have been intended to close the
|
|
* surrounding formatted string instead of opening a string
|
|
* inside a replacement field. */
|
|
if (state->quote == quote && state->quote_size == quote_size) {
|
|
int level = state->paren_level + state->replacement_depth - 1;
|
|
assert(level >= 0 && level < tok->level);
|
|
assert(tok->parenstack[level] == '{');
|
|
int lineno = tok->parenlinenostack[level];
|
|
if (lineno != location.lineno) {
|
|
_PyTokenizer_syntaxerror_at(
|
|
tok, line, cursor_offset, location.lineno, -1, -1,
|
|
"%c-string: expecting '}' to close '{' on line %d",
|
|
_PyLexer_StringPrefix(state->kind), lineno);
|
|
}
|
|
else {
|
|
_PyTokenizer_syntaxerror_at(
|
|
tok, line, cursor_offset, location.lineno, -1, -1,
|
|
"%c-string: expecting '}'",
|
|
_PyLexer_StringPrefix(state->kind));
|
|
}
|
|
return string_error_token(tok, token, tok->start, location);
|
|
}
|
|
}
|
|
|
|
if (quote_size == 3) {
|
|
_PyTokenizer_syntaxerror_at(
|
|
tok, line, cursor_offset, location.lineno, -1, -1,
|
|
"unterminated triple-quoted string literal"
|
|
" (detected at line %d)", end_lineno);
|
|
if (c != '\n') {
|
|
tok->done = E_EOFS;
|
|
}
|
|
return string_error_token(tok, token, tok->start, location);
|
|
}
|
|
else {
|
|
if (has_escaped_quote) {
|
|
_PyTokenizer_syntaxerror_at(
|
|
tok, line, cursor_offset, location.lineno, -1, -1,
|
|
"unterminated string literal (detected at line %d); "
|
|
"perhaps you escaped the end quote?",
|
|
end_lineno
|
|
);
|
|
} else {
|
|
_PyTokenizer_syntaxerror_at(
|
|
tok, line, cursor_offset, location.lineno, -1, -1,
|
|
"unterminated string literal (detected at line %d)", end_lineno
|
|
);
|
|
}
|
|
if (c != '\n') {
|
|
tok->done = E_EOLS;
|
|
}
|
|
return string_error_token(tok, token, tok->start, location);
|
|
}
|
|
}
|
|
if (c == quote) {
|
|
end_quote_size += 1;
|
|
}
|
|
else {
|
|
end_quote_size = 0;
|
|
if (c == '\\') {
|
|
c = tok_nextc(tok); /* skip escaped char */
|
|
if (c == quote) { /* but record whether the escaped char was a quote */
|
|
has_escaped_quote = 1;
|
|
}
|
|
if (c == '\r') {
|
|
c = tok_nextc(tok);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
p_start = tok->start;
|
|
p_end = tok->cur;
|
|
return MAKE_TOKEN(STRING);
|
|
}
|
|
|
|
int
|
|
_PyLexer_get_ftstring(struct tok_state *tok, ftstring_state *current, struct token *token)
|
|
{
|
|
assert(current == _PyLexer_CurrentFTString(tok) && current->mode != FTSTRING_MODE_EXPRESSION);
|
|
assert((current->quote_size == 1 || current->quote_size == 3) &&
|
|
current->replacement_depth <= MAX_EXPR_NESTING);
|
|
_PyTok_Off p_start = -1;
|
|
_PyTok_Off p_end = -1;
|
|
int token_type;
|
|
int end_quote_size = 0;
|
|
int unicode_escape = 0;
|
|
int quote = current->quote;
|
|
int quote_size = current->quote_size;
|
|
int in_format_spec = current->mode == FTSTRING_MODE_FORMAT_SPEC;
|
|
int raw = _PyLexer_IsRawString(current->kind);
|
|
|
|
tok->start = tok->cur;
|
|
tok->start_loc = (_PyTok_Loc){tok->lineno, _PyLexer_ByteColumn(tok)};
|
|
|
|
while (end_quote_size != quote_size) {
|
|
int c = tok_nextc(tok);
|
|
if (tok->done == E_ERROR || tok->done == E_DECODE) {
|
|
return MAKE_TOKEN(ERRORTOKEN);
|
|
}
|
|
|
|
if (c == EOF || (quote_size == 1 && c == '\n')) {
|
|
if (tok_failed(tok)) {
|
|
return MAKE_TOKEN(ERRORTOKEN);
|
|
}
|
|
if (c == EOF && tok->allow_incomplete_input) {
|
|
int type = incomplete_string_token(tok, token,
|
|
FTSTRING_MIDDLE(current), current->start, current->start_loc);
|
|
// Preserve the actual prefix for incremental escape decoding.
|
|
_PyTok_Off end = tok->cur;
|
|
if (end > tok->start && tok->implicit_newline) {
|
|
end--;
|
|
}
|
|
token->span = (_PyTok_Span){tok->start, end};
|
|
token->start_loc = tok->start_loc;
|
|
token->end_loc = (_PyTok_Loc){tok->lineno, (int)(end - tok->line_start)};
|
|
token->is_raw = raw;
|
|
return type;
|
|
}
|
|
|
|
if (in_format_spec && c == '\n') {
|
|
return MAKE_TOKEN(_PyTokenizer_syntaxerror(
|
|
tok,
|
|
"%c-string: newlines are not allowed in format specifiers for single quoted %c-strings",
|
|
_PyLexer_StringPrefix(current->kind),
|
|
_PyLexer_StringPrefix(current->kind)));
|
|
}
|
|
|
|
int end_lineno = tok->lineno;
|
|
_PyTok_Loc location = current->start_loc;
|
|
const char *line = _PyTok_SourcePointer(
|
|
&tok->source, current->start - location.byte_col);
|
|
Py_ssize_t cursor_offset = (Py_ssize_t)location.byte_col + 1;
|
|
|
|
if (quote_size == 3) {
|
|
_PyTokenizer_syntaxerror_at(
|
|
tok, line, cursor_offset, location.lineno, -1, -1,
|
|
"unterminated triple-quoted %c-string literal"
|
|
" (detected at line %d)",
|
|
_PyLexer_StringPrefix(current->kind), end_lineno);
|
|
if (c != '\n') {
|
|
tok->done = E_EOFS;
|
|
}
|
|
return string_error_token(tok, token,
|
|
current->start, location);
|
|
}
|
|
else {
|
|
_PyTokenizer_syntaxerror_at(
|
|
tok, line, cursor_offset, location.lineno, -1, -1,
|
|
"unterminated %c-string literal (detected at line %d)",
|
|
_PyLexer_StringPrefix(current->kind), end_lineno);
|
|
return string_error_token(tok, token,
|
|
current->start, location);
|
|
}
|
|
}
|
|
|
|
if (c == quote) {
|
|
end_quote_size += 1;
|
|
continue;
|
|
} else {
|
|
end_quote_size = 0;
|
|
}
|
|
|
|
if (c == '{') {
|
|
int peek = tok_nextc(tok);
|
|
if (peek != '{' || in_format_spec) {
|
|
tok_backup(tok, peek);
|
|
_PyTok_Off expr_start = tok->cur;
|
|
tok_backup(tok, c);
|
|
if (begin_ftstring_expr(tok, current, expr_start) < 0) {
|
|
return MAKE_TOKEN(ERRORTOKEN);
|
|
}
|
|
p_start = tok->start;
|
|
p_end = tok->cur;
|
|
if (p_start == p_end) {
|
|
return _PyLexer_get_normal(tok, current, token);
|
|
}
|
|
} else {
|
|
p_start = tok->start;
|
|
p_end = tok->cur - 1;
|
|
}
|
|
goto emit_middle;
|
|
} else if (c == '}') {
|
|
if (unicode_escape) {
|
|
p_start = tok->start;
|
|
p_end = tok->cur;
|
|
goto emit_middle;
|
|
}
|
|
int peek = tok_nextc(tok);
|
|
|
|
int bracket_depth = _PyLexer_FTStringBracketDepth(tok, current);
|
|
if (peek == '}' && !in_format_spec && bracket_depth == 0) {
|
|
p_start = tok->start;
|
|
p_end = tok->cur - 1;
|
|
}
|
|
else {
|
|
tok_backup(tok, peek);
|
|
if (!in_format_spec && bracket_depth == 0) {
|
|
if (tok->start == tok->cur - 1) {
|
|
return MAKE_TOKEN(_PyTokenizer_syntaxerror(
|
|
tok, "%c-string: single '}' is not allowed",
|
|
_PyLexer_StringPrefix(current->kind)));
|
|
}
|
|
tok_backup(tok, c);
|
|
p_start = tok->start;
|
|
p_end = tok->cur;
|
|
goto emit_middle;
|
|
}
|
|
tok_backup(tok, c);
|
|
current->mode = FTSTRING_MODE_EXPRESSION;
|
|
p_start = tok->start;
|
|
p_end = tok->cur;
|
|
}
|
|
goto emit_middle;
|
|
} else if (c == '\\') {
|
|
int peek = tok_nextc(tok);
|
|
if (peek == '\r') {
|
|
peek = tok_nextc(tok);
|
|
}
|
|
if (peek == '{' || peek == '}') {
|
|
if (!raw) {
|
|
if (_PyTokenizer_warn_invalid_escape_sequence(tok, peek)) {
|
|
return MAKE_TOKEN(ERRORTOKEN);
|
|
}
|
|
}
|
|
tok_backup(tok, peek);
|
|
continue;
|
|
}
|
|
|
|
if (!raw) {
|
|
if (peek == 'N') {
|
|
/* Handle named unicode escapes (\N{BULLET}) */
|
|
peek = tok_nextc(tok);
|
|
if (peek == '{') {
|
|
unicode_escape = 1;
|
|
} else {
|
|
tok_backup(tok, peek);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
p_start = tok->start;
|
|
p_end = tok->cur;
|
|
if (p_end - quote_size == p_start) {
|
|
int end_token = FTSTRING_END(current);
|
|
_PyLexer_PopFTString(tok);
|
|
return MAKE_TOKEN(end_token);
|
|
}
|
|
for (int i = 0; i < quote_size; i++) {
|
|
tok_backup(tok, quote);
|
|
}
|
|
p_end = tok->cur;
|
|
emit_middle:
|
|
token_type = MAKE_TOKEN(FTSTRING_MIDDLE(current));
|
|
token->is_raw = raw;
|
|
return token_type;
|
|
}
|