| #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; |
| 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) { |
| return incomplete_string_token(tok, token, |
| FTSTRING_MIDDLE(current), current->start, current->start_loc); |
| } |
| |
| 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; |
| } |