blob: c2bef9773eab937ad2a11e6206fef66c05e198c7 [file]
#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;
}