| #include <Python.h> | |
| #include <errcode.h> | |
| #include "pycore_pyerrors.h" // _PyErr_ProgramDecodedTextObject() | |
| #include "pycore_runtime.h" // _Py_ID() | |
| #include "pycore_tuple.h" // _PyTuple_FromPair | |
| #include "pegen.h" | |
| #include "tokenizer/tokenizer.h" | |
| // TOKENIZER ERRORS | |
| void * | |
| _PyPegen_raise_incomplete_input(Parser *p) | |
| { | |
| if ((p->flags & PyPARSE_ALLOW_INCOMPLETE_INPUT) && !PyErr_Occurred()) { | |
| return _PyPegen_raise_error(p, PyExc_IncompleteInputError, 0, | |
| "incomplete input"); | |
| } | |
| return NULL; | |
| } | |
| static inline void | |
| raise_unclosed_parentheses_error(Parser *p) { | |
| _PyTokenizer_Info info = _PyTokenizer_GetInfo(p->tok); | |
| int error_lineno = info.delimiter_loc.lineno; | |
| int error_col = info.delimiter_loc.byte_col; | |
| RAISE_ERROR_KNOWN_LOCATION(p, PyExc_SyntaxError, | |
| error_lineno, error_col, error_lineno, -1, | |
| "'%c' was never closed", | |
| info.delimiter); | |
| } | |
| int | |
| _Pypegen_tokenizer_error(Parser *p) | |
| { | |
| if (PyErr_Occurred()) { | |
| return -1; | |
| } | |
| const char *msg = NULL; | |
| PyObject* errtype = PyExc_SyntaxError; | |
| Py_ssize_t col_offset = -1; | |
| p->error_indicator = 1; | |
| _PyTokenizer_Info info = _PyTokenizer_GetInfo(p->tok); | |
| if ((p->flags & PyPARSE_ALLOW_INCOMPLETE_INPUT) && | |
| (info.status == E_EOF || info.status == E_INCOMPLETE)) | |
| { | |
| _PyPegen_raise_incomplete_input(p); | |
| return -1; | |
| } | |
| switch (info.status) { | |
| case E_INCOMPLETE: | |
| // The diagnostic pass rejected this unfinished token. Let the | |
| // parser report the syntax error at its original failure point. | |
| return -1; | |
| case E_TOKEN: | |
| msg = "invalid token"; | |
| break; | |
| case E_EOF: | |
| if (info.level) { | |
| raise_unclosed_parentheses_error(p); | |
| } else { | |
| RAISE_SYNTAX_ERROR("unexpected EOF while parsing"); | |
| } | |
| return -1; | |
| case E_DEDENT: | |
| RAISE_INDENTATION_ERROR("unindent does not match any outer indentation level"); | |
| return -1; | |
| case E_INTR: | |
| if (!PyErr_Occurred()) { | |
| PyErr_SetNone(PyExc_KeyboardInterrupt); | |
| } | |
| return -1; | |
| case E_NOMEM: | |
| PyErr_NoMemory(); | |
| return -1; | |
| case E_TABSPACE: | |
| errtype = PyExc_TabError; | |
| msg = "inconsistent use of tabs and spaces in indentation"; | |
| break; | |
| case E_TOODEEP: | |
| errtype = PyExc_IndentationError; | |
| msg = "too many levels of indentation"; | |
| break; | |
| case E_LINECONT: { | |
| col_offset = info.cursor - info.line_span.start - 1; | |
| msg = "unexpected character after line continuation character"; | |
| break; | |
| } | |
| case E_COLUMNOVERFLOW: | |
| PyErr_SetString(PyExc_OverflowError, | |
| "Parser column offset overflow - source line is too big"); | |
| return -1; | |
| default: | |
| msg = "unknown parsing error"; | |
| } | |
| RAISE_ERROR_KNOWN_LOCATION(p, errtype, info.location.lineno, | |
| col_offset >= 0 ? col_offset : 0, | |
| info.location.lineno, -1, msg); | |
| return -1; | |
| } | |
| int | |
| _Pypegen_raise_decode_error(Parser *p) | |
| { | |
| assert(PyErr_Occurred()); | |
| const char *errtype = NULL; | |
| if (PyErr_ExceptionMatches(PyExc_UnicodeError)) { | |
| errtype = "unicode error"; | |
| } | |
| else if (PyErr_ExceptionMatches(PyExc_ValueError)) { | |
| errtype = "value error"; | |
| } | |
| if (errtype) { | |
| PyObject *type; | |
| PyObject *value; | |
| PyObject *tback; | |
| PyObject *errstr; | |
| PyErr_Fetch(&type, &value, &tback); | |
| errstr = PyObject_Str(value); | |
| if (errstr) { | |
| RAISE_SYNTAX_ERROR("(%s) %U", errtype, errstr); | |
| Py_DECREF(errstr); | |
| } | |
| else { | |
| PyErr_Clear(); | |
| RAISE_SYNTAX_ERROR("(%s) unknown error", errtype); | |
| } | |
| Py_XDECREF(type); | |
| Py_XDECREF(value); | |
| Py_XDECREF(tback); | |
| } | |
| return -1; | |
| } | |
| static int | |
| _PyPegen_tokenize_full_source_to_check_for_errors(Parser *p) { | |
| // Tokenize the whole input to see if there are any tokenization | |
| // errors such as mismatching parentheses. These will get priority | |
| // over generic syntax errors only if the line number of the error is | |
| // before the one that we had for the generic error. | |
| // We don't want to tokenize to the end for interactive input | |
| if (_PyTokenizer_IsInteractive(p->tok)) { | |
| return 0; | |
| } | |
| PyObject *type, *value, *traceback; | |
| PyErr_Fetch(&type, &value, &traceback); | |
| Token *current_token = p->known_err_token != NULL ? p->known_err_token : p->tokens[p->fill - 1]; | |
| Py_ssize_t current_err_line = current_token->lineno; | |
| int ret = 0; | |
| struct token new_token; | |
| _PyToken_Init(&new_token); | |
| for (;;) { | |
| _PyTokenizer_Get(p->tok, &new_token); | |
| switch (new_token.type) { | |
| case ERRORTOKEN: { | |
| if (PyErr_Occurred()) { | |
| ret = -1; | |
| goto exit; | |
| } | |
| _PyTokenizer_Info info = _PyTokenizer_GetInfo(p->tok); | |
| if (info.level != 0) { | |
| int error_lineno = info.delimiter_loc.lineno; | |
| if (current_err_line > error_lineno) { | |
| raise_unclosed_parentheses_error(p); | |
| ret = -1; | |
| goto exit; | |
| } | |
| } | |
| break; | |
| } | |
| case ENDMARKER: | |
| break; | |
| default: | |
| continue; | |
| } | |
| break; | |
| } | |
| exit: | |
| _PyToken_Free(&new_token); | |
| _PyTokenizer_Info info = _PyTokenizer_GetInfo(p->tok); | |
| // Preserve expression errors over later formatted-string errors. | |
| if (PyErr_Occurred() && !info.in_formatted_string) { | |
| Py_XDECREF(value); | |
| Py_XDECREF(type); | |
| Py_XDECREF(traceback); | |
| } else { | |
| PyErr_Restore(type, value, traceback); | |
| } | |
| return ret; | |
| } | |
| // PARSER ERRORS | |
| void * | |
| _PyPegen_raise_error(Parser *p, PyObject *errtype, int use_mark, const char *errmsg, ...) | |
| { | |
| // Bail out if we already have an error set. | |
| if (p->error_indicator && PyErr_Occurred()) { | |
| return NULL; | |
| } | |
| if (p->fill == 0) { | |
| va_list va; | |
| va_start(va, errmsg); | |
| _PyPegen_raise_error_known_location(p, errtype, 0, 0, 0, -1, errmsg, va); | |
| va_end(va); | |
| return NULL; | |
| } | |
| if (use_mark && p->mark == p->fill && _PyPegen_fill_token(p) < 0) { | |
| p->error_indicator = 1; | |
| return NULL; | |
| } | |
| Token *t = p->known_err_token != NULL | |
| ? p->known_err_token | |
| : p->tokens[use_mark ? p->mark : p->fill - 1]; | |
| Py_ssize_t col_offset; | |
| Py_ssize_t end_col_offset = -1; | |
| if (t->col_offset == -1) { | |
| _PyTokenizer_Info info = _PyTokenizer_GetInfo(p->tok); | |
| if (info.diagnostic.location.lineno != 0) { | |
| col_offset = info.diagnostic.location.byte_col; | |
| } else if (info.cursor == info.input_span.start) { | |
| col_offset = 0; | |
| } else { | |
| col_offset = Py_SAFE_DOWNCAST( | |
| info.cursor - info.line_span.start, intptr_t, int); | |
| } | |
| } else { | |
| col_offset = t->col_offset + 1; | |
| } | |
| if (t->end_col_offset != -1) { | |
| end_col_offset = t->end_col_offset + 1; | |
| } | |
| va_list va; | |
| va_start(va, errmsg); | |
| _PyPegen_raise_error_known_location(p, errtype, t->lineno, col_offset, t->end_lineno, end_col_offset, errmsg, va); | |
| va_end(va); | |
| return NULL; | |
| } | |
| static PyObject * | |
| get_error_line_from_source(Parser *p, Py_ssize_t lineno) | |
| { | |
| if (_PyTokenizer_RetainedSource(p->tok) == NULL) { | |
| return Py_GetConstant(Py_CONSTANT_EMPTY_STR); | |
| } | |
| Py_ssize_t relative_lineno = p->starting_lineno ? lineno - p->starting_lineno + 1 : lineno; | |
| Py_ssize_t len; | |
| const char *line = _PyTokenizer_LineView(p->tok, relative_lineno, &len); | |
| return PyUnicode_DecodeUTF8(line, len, "replace"); | |
| } | |
| void * | |
| _PyPegen_raise_error_known_location(Parser *p, PyObject *errtype, | |
| Py_ssize_t lineno, Py_ssize_t col_offset, | |
| Py_ssize_t end_lineno, Py_ssize_t end_col_offset, | |
| const char *errmsg, va_list va) | |
| { | |
| // Bail out if we already have an error set. | |
| if (p->error_indicator && PyErr_Occurred()) { | |
| return NULL; | |
| } | |
| PyObject *value = NULL; | |
| PyObject *errstr = NULL; | |
| PyObject *error_line = NULL; | |
| PyObject *tmp = NULL; | |
| p->error_indicator = 1; | |
| _PyTokenizer_Info info = _PyTokenizer_GetInfo(p->tok); | |
| _PyTok_Loc location = info.diagnostic.location.lineno != 0 | |
| ? info.diagnostic.location : info.location; | |
| _PyTok_Span text_span = info.diagnostic.location.lineno != 0 | |
| ? info.diagnostic.text_span : info.line_span; | |
| if (end_lineno == CURRENT_POS) { | |
| end_lineno = location.lineno; | |
| } | |
| if (end_col_offset == CURRENT_POS) { | |
| end_col_offset = location.byte_col; | |
| } | |
| errstr = PyUnicode_FromFormatV(errmsg, va); | |
| if (!errstr) { | |
| goto error; | |
| } | |
| if (info.is_interactive && _PyTokenizer_RetainedSource(p->tok) != NULL) { | |
| error_line = get_error_line_from_source(p, lineno); | |
| } | |
| else if (p->start_rule == Py_file_input) { | |
| error_line = _PyErr_ProgramDecodedTextObject(info.filename, | |
| (int) lineno, info.encoding); | |
| } | |
| if (!error_line) { | |
| /* PyErr_ProgramTextObject was not called or returned NULL. If it was not called, | |
| then we need to find the error line from some other source, because | |
| p->start_rule != Py_file_input. If it returned NULL, then it either unexpectedly | |
| failed or we're parsing from a string or the REPL. There's a third edge case where | |
| we're actually parsing from a file, which has an E_EOF SyntaxError and in that case | |
| `PyErr_ProgramTextObject` fails because lineno points to last_file_line + 1, which | |
| does not physically exist */ | |
| assert(!info.is_file || info.status == E_EOF); | |
| if (location.lineno <= lineno && | |
| info.input_span.end > info.input_span.start) { | |
| Py_ssize_t size; | |
| const char *line = _PyTokenizer_SpanView( | |
| p->tok, text_span, &size); | |
| if (errtype == PyExc_IncompleteInputError && size > 0 && | |
| (info.implicit_newline == _PyTokenizer_APPEND_NEWLINE || | |
| (info.implicit_newline == _PyTokenizer_IMPLY_NEWLINE && | |
| p->start_rule != Py_file_input))) { | |
| size--; | |
| } | |
| error_line = PyUnicode_DecodeUTF8(line, size, "replace"); | |
| } | |
| else if (!info.is_file) { | |
| error_line = get_error_line_from_source(p, lineno); | |
| } | |
| else { | |
| error_line = Py_GetConstant(Py_CONSTANT_EMPTY_STR); | |
| } | |
| if (!error_line) { | |
| goto error; | |
| } | |
| } | |
| Py_ssize_t col_number = col_offset; | |
| Py_ssize_t end_col_number = end_col_offset; | |
| col_number = _PyPegen_byte_offset_to_character_offset(error_line, col_offset); | |
| if (col_number < 0) { | |
| goto error; | |
| } | |
| if (end_col_offset > 0) { | |
| end_col_number = _PyPegen_byte_offset_to_character_offset(error_line, end_col_offset); | |
| if (end_col_number < 0) { | |
| goto error; | |
| } | |
| } | |
| tmp = Py_BuildValue("(OnnNnn)", info.filename, lineno, col_number, error_line, end_lineno, end_col_number); | |
| error_line = NULL; | |
| if (!tmp) { | |
| goto error; | |
| } | |
| value = _PyTuple_FromPair(errstr, tmp); | |
| Py_DECREF(tmp); | |
| if (!value) { | |
| goto error; | |
| } | |
| PyErr_SetObject(errtype, value); | |
| Py_DECREF(errstr); | |
| Py_DECREF(value); | |
| return NULL; | |
| error: | |
| Py_XDECREF(errstr); | |
| Py_XDECREF(error_line); | |
| return NULL; | |
| } | |
| void | |
| _Pypegen_set_syntax_error(Parser* p, Token* last_token) { | |
| _PyTokenizer_Info info = _PyTokenizer_GetInfo(p->tok); | |
| // Existing syntax error | |
| if (PyErr_Occurred()) { | |
| // Prioritize tokenizer errors to custom syntax errors raised | |
| // on the second phase only if the errors come from the parser. | |
| int is_tok_ok = (info.status == E_DONE || info.status == E_OK); | |
| if (is_tok_ok && PyErr_ExceptionMatches(PyExc_SyntaxError)) { | |
| _PyPegen_tokenize_full_source_to_check_for_errors(p); | |
| } | |
| // Propagate the existing syntax error. | |
| return; | |
| } | |
| // Initialization error | |
| if (p->fill == 0) { | |
| RAISE_SYNTAX_ERROR("error at start before reading any input"); | |
| } | |
| // Parser encountered EOF (End of File) unexpectedtly | |
| if (last_token->type == ERRORTOKEN && info.status == E_EOF) { | |
| if (info.level) { | |
| raise_unclosed_parentheses_error(p); | |
| } else { | |
| RAISE_SYNTAX_ERROR("unexpected EOF while parsing"); | |
| } | |
| return; | |
| } | |
| // Indentation error in the tokenizer | |
| if (last_token->type == INDENT || last_token->type == DEDENT) { | |
| RAISE_INDENTATION_ERROR(last_token->type == INDENT ? "unexpected indent" : "unexpected unindent"); | |
| return; | |
| } | |
| // Unknown error (generic case) | |
| // Use the last token we found on the first pass to avoid reporting | |
| // incorrect locations for generic syntax errors just because we reached | |
| // further away when trying to find specific syntax errors in the second | |
| // pass. | |
| RAISE_SYNTAX_ERROR_KNOWN_LOCATION(last_token, "invalid syntax"); | |
| // _PyPegen_tokenize_full_source_to_check_for_errors will override the existing | |
| // generic SyntaxError we just raised if errors are found. | |
| _PyPegen_tokenize_full_source_to_check_for_errors(p); | |
| } | |
| void | |
| _Pypegen_stack_overflow(Parser *p) | |
| { | |
| p->error_indicator = 1; | |
| PyErr_SetString(PyExc_MemoryError, | |
| "Parser stack overflowed - Python source too complex to parse"); | |
| } |