| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -18,8 +18,10 @@ | |||
| 18 | 18 | #define PyCF_IGNORE_COOKIE 0x0800 | |
| 19 | 19 | #define PyCF_TYPE_COMMENTS 0x1000 | |
| 20 | 20 | #define PyCF_ALLOW_TOP_LEVEL_AWAIT 0x2000 | |
| 21 | + #define PyCF_ALLOW_INCOMPLETE_INPUT 0x4000 | ||
| 21 | 22 | #define PyCF_COMPILE_MASK (PyCF_ONLY_AST | PyCF_ALLOW_TOP_LEVEL_AWAIT | \ | |
| 22 | - PyCF_TYPE_COMMENTS | PyCF_DONT_IMPLY_DEDENT) | ||
| 23 | + PyCF_TYPE_COMMENTS | PyCF_DONT_IMPLY_DEDENT | \ | ||
| 24 | + PyCF_ALLOW_INCOMPLETE_INPUT) | ||
| 23 | 25 | ||
| 24 | 26 | typedef struct { | |
| 25 | 27 | int cf_flags; /* bitmask of CO_xxx flags relevant to future */ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -26,6 +26,8 @@ extern "C" { | |||
| 26 | 26 | #define E_TOODEEP 20 /* Too many indentation levels */ | |
| 27 | 27 | #define E_DEDENT 21 /* No matching outer block for dedent */ | |
| 28 | 28 | #define E_DECODE 22 /* Error in decoding into Unicode */ | |
| 29 | + #define E_EOFS 23 /* EOF in triple-quoted string */ | ||
| 30 | + #define E_EOLS 24 /* EOL in single-quoted string */ | ||
| 29 | 31 | #define E_LINECONT 25 /* Unexpected characters after a line continuation */ | |
| 30 | 32 | #define E_BADSINGLE 27 /* Ill-formed single statement input */ | |
| 31 | 33 | #define E_INTERACT_STOP 28 /* Interactive mode stopped tokenization */ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -10,30 +10,6 @@ | |||
| 10 | 10 | syntax error (OverflowError and ValueError can be produced by | |
| 11 | 11 | malformed literals). | |
| 12 | 12 | ||
| 13 | - Approach: | ||
| 14 | - | ||
| 15 | - First, check if the source consists entirely of blank lines and | ||
| 16 | - comments; if so, replace it with 'pass', because the built-in | ||
| 17 | - parser doesn't always do the right thing for these. | ||
| 18 | - | ||
| 19 | - Compile three times: as is, with \n, and with \n\n appended. If it | ||
| 20 | - compiles as is, it's complete. If it compiles with one \n appended, | ||
| 21 | - we expect more. If it doesn't compile either way, we compare the | ||
| 22 | - error we get when compiling with \n or \n\n appended. If the errors | ||
| 23 | - are the same, the code is broken. But if the errors are different, we | ||
| 24 | - expect more. Not intuitive; not even guaranteed to hold in future | ||
| 25 | - releases; but this matches the compiler's behavior from Python 1.4 | ||
| 26 | - through 2.2, at least. | ||
| 27 | - | ||
| 28 | - Caveat: | ||
| 29 | - | ||
| 30 | - It is possible (but not likely) that the parser stops parsing with a | ||
| 31 | - successful outcome before reaching the end of the source; in this | ||
| 32 | - case, trailing symbols may be ignored instead of causing an error. | ||
| 33 | - For example, a backslash followed by two newlines may be followed by | ||
| 34 | - arbitrary garbage. This will be fixed once the API for the parser is | ||
| 35 | - better. | ||
| 36 | - | ||
| 37 | 13 | The two interfaces are: | |
| 38 | 14 | ||
| 39 | 15 | compile_command(source, filename, symbol): | |
@@ -64,7 +40,11 @@ | |||
| 64 | 40 | ||
| 65 | 41 | __all__ = ["compile_command", "Compile", "CommandCompiler"] | |
| 66 | 42 | ||
| 67 | - PyCF_DONT_IMPLY_DEDENT = 0x200 # Matches pythonrun.h. | ||
| 43 | + # The following flags match the values from Include/cpython/compile.h | ||
| 44 | + # Caveat emptor: These flags are undocumented on purpose and depending | ||
| 45 | + # on their effect outside the standard library is **unsupported**. | ||
| 46 | + PyCF_DONT_IMPLY_DEDENT = 0x200 | ||
| 47 | + PyCF_ALLOW_INCOMPLETE_INPUT = 0x4000 | ||
| 68 | 48 | ||
| 69 | 49 | def _maybe_compile(compiler, source, filename, symbol): | |
| 70 | 50 | # Check for source consisting of only blank lines and comments. | |
@@ -86,24 +66,12 @@ def _maybe_compile(compiler, source, filename, symbol): | |||
| 86 | 66 | with warnings.catch_warnings(): | |
| 87 | 67 | warnings.simplefilter("error") | |
| 88 | 68 | ||
| 89 | - code1 = err1 = err2 = None | ||
| 90 | - try: | ||
| 91 | - code1 = compiler(source + "\n", filename, symbol) | ||
| 92 | - except SyntaxError as e: | ||
| 93 | - err1 = e | ||
| 94 | - | ||
| 95 | 69 | try: | |
| 96 | - code2 = compiler(source + "\n\n", filename, symbol) | ||
| 70 | + compiler(source + "\n", filename, symbol) | ||
| 97 | 71 | except SyntaxError as e: | |
| 98 | - err2 = e | ||
| 99 | - | ||
| 100 | - try: | ||
| 101 | - if not code1 and _is_syntax_error(err1, err2): | ||
| 102 | - raise err1 | ||
| 103 | - else: | ||
| 104 | - return None | ||
| 105 | - finally: | ||
| 106 | - err1 = err2 = None | ||
| 72 | + if "incomplete input" in str(e): | ||
| 73 | + return None | ||
| 74 | + raise | ||
| 107 | 75 | ||
| 108 | 76 | def _is_syntax_error(err1, err2): | |
| 109 | 77 | rep1 = repr(err1) | |
@@ -115,7 +83,7 @@ def _is_syntax_error(err1, err2): | |||
| 115 | 83 | return False | |
| 116 | 84 | ||
| 117 | 85 | def _compile(source, filename, symbol): | |
| 118 | - return compile(source, filename, symbol, PyCF_DONT_IMPLY_DEDENT) | ||
| 86 | + return compile(source, filename, symbol, PyCF_DONT_IMPLY_DEDENT | PyCF_ALLOW_INCOMPLETE_INPUT) | ||
| 119 | 87 | ||
| 120 | 88 | def compile_command(source, filename="<input>", symbol="single"): | |
| 121 | 89 | r"""Compile a command and determine whether it is incomplete. | |
@@ -144,7 +112,7 @@ class Compile: | |||
| 144 | 112 | statement, it "remembers" and compiles all subsequent program texts | |
| 145 | 113 | with the statement in force.""" | |
| 146 | 114 | def __init__(self): | |
| 147 | - self.flags = PyCF_DONT_IMPLY_DEDENT | ||
| 115 | + self.flags = PyCF_DONT_IMPLY_DEDENT | PyCF_ALLOW_INCOMPLETE_INPUT | ||
| 148 | 116 | ||
| 149 | 117 | def __call__(self, source, filename, symbol): | |
| 150 | 118 | codeob = compile(source, filename, symbol, self.flags, True) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,2 @@ | |||
| 1 | + Fix a bug in the :mod:`codeop` module that was incorrectly identifying | ||
| 2 | + invalid code involving string quotes as valid code. | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -726,6 +726,9 @@ compute_parser_flags(PyCompilerFlags *flags) | |||
| 726 | 726 | if ((flags->cf_flags & PyCF_ONLY_AST) && flags->cf_feature_version < 7) { | |
| 727 | 727 | parser_flags |= PyPARSE_ASYNC_HACKS; | |
| 728 | 728 | } | |
| 729 | + if (flags->cf_flags & PyCF_ALLOW_INCOMPLETE_INPUT) { | ||
| 730 | + parser_flags |= PyPARSE_ALLOW_INCOMPLETE_INPUT; | ||
| 731 | + } | ||
| 729 | 732 | return parser_flags; | |
| 730 | 733 | } | |
| 731 | 734 | ||
@@ -811,16 +814,26 @@ reset_parser_state_for_error_pass(Parser *p) | |||
| 811 | 814 | p->tok->interactive_underflow = IUNDERFLOW_STOP; | |
| 812 | 815 | } | |
| 813 | 816 | ||
| 817 | + static inline int | ||
| 818 | + _is_end_of_source(Parser *p) { | ||
| 819 | + int err = p->tok->done; | ||
| 820 | + return err == E_EOF || err == E_EOFS || err == E_EOLS; | ||
| 821 | + } | ||
| 822 | + | ||
| 814 | 823 | void * | |
| 815 | 824 | _PyPegen_run_parser(Parser *p) | |
| 816 | 825 | { | |
| 817 | 826 | void *res = _PyPegen_parse(p); | |
| 818 | 827 | assert(p->level == 0); | |
| 819 | 828 | if (res == NULL) { | |
| 829 | + if ((p->flags & PyPARSE_ALLOW_INCOMPLETE_INPUT) && _is_end_of_source(p)) { | ||
| 830 | + PyErr_Clear(); | ||
| 831 | + return RAISE_SYNTAX_ERROR("incomplete input"); | ||
| 832 | + } | ||
| 820 | 833 | if (PyErr_Occurred() && !PyErr_ExceptionMatches(PyExc_SyntaxError)) { | |
| 821 | 834 | return NULL; | |
| 822 | 835 | } | |
| 823 | - // Make a second parser pass. In this pass we activate heavier and slower checks | ||
| 836 | + // Make a second parser pass. In this pass we activate heavier and slower checks | ||
| 824 | 837 | // to produce better error messages and more complete diagnostics. Extra "invalid_*" | |
| 825 | 838 | // rules will be active during parsing. | |
| 826 | 839 | Token *last_token = p->tokens[p->fill - 1]; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -22,6 +22,7 @@ | |||
| 22 | 22 | #define PyPARSE_BARRY_AS_BDFL 0x0020 | |
| 23 | 23 | #define PyPARSE_TYPE_COMMENTS 0x0040 | |
| 24 | 24 | #define PyPARSE_ASYNC_HACKS 0x0080 | |
| 25 | + #define PyPARSE_ALLOW_INCOMPLETE_INPUT 0x0100 | ||
| 25 | 26 | ||
| 26 | 27 | #define CURRENT_POS (-5) | |
| 27 | 28 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -40,7 +40,7 @@ | |||
| 40 | 40 | static struct tok_state *tok_new(void); | |
| 41 | 41 | static int tok_nextc(struct tok_state *tok); | |
| 42 | 42 | static void tok_backup(struct tok_state *tok, int c); | |
| 43 | - | ||
| 43 | + static int syntaxerror(struct tok_state *tok, const char *format, ...); | ||
| 44 | 44 | ||
| 45 | 45 | /* Spaces in this constant are treated as "zero or more spaces or tabs" when | |
| 46 | 46 | tokenizing. */ | |
@@ -1031,8 +1031,9 @@ tok_nextc(struct tok_state *tok) | |||
| 1031 | 1031 | if (tok->cur != tok->inp) { | |
| 1032 | 1032 | return Py_CHARMASK(*tok->cur++); /* Fast path */ | |
| 1033 | 1033 | } | |
| 1034 | - if (tok->done != E_OK) | ||
| 1035 | - return EOF; | ||
| 1034 | + if (tok->done != E_OK) { | ||
| 1035 | + return EOF; | ||
| 1036 | + } | ||
| 1036 | 1037 | if (tok->fp == NULL) { | |
| 1037 | 1038 | rc = tok_underflow_string(tok); | |
| 1038 | 1039 | } | |
@@ -1964,16 +1965,21 @@ tok_get(struct tok_state *tok, const char **p_start, const char **p_end) | |||
| 1964 | 1965 | tok->line_start = tok->multi_line_start; | |
| 1965 | 1966 | int start = tok->lineno; | |
| 1966 | 1967 | tok->lineno = tok->first_lineno; | |
| 1967 | - | ||
| 1968 | 1968 | if (quote_size == 3) { | |
| 1969 | - return syntaxerror(tok, | ||
| 1970 | - "unterminated triple-quoted string literal" | ||
| 1971 | - " (detected at line %d)", start); | ||
| 1969 | + syntaxerror(tok, "unterminated triple-quoted string literal" | ||
| 1970 | + " (detected at line %d)", start); | ||
| 1971 | + if (c != '\n') { | ||
| 1972 | + tok->done = E_EOFS; | ||
| 1973 | + } | ||
| 1974 | + return ERRORTOKEN; | ||
| 1972 | 1975 | } | |
| 1973 | 1976 | else { | |
| 1974 | - return syntaxerror(tok, | ||
| 1975 | - "unterminated string literal (detected at" | ||
| 1976 | - " line %d)", start); | ||
| 1977 | + syntaxerror(tok, "unterminated string literal (detected at" | ||
| 1978 | + " line %d)", start); | ||
| 1979 | + if (c != '\n') { | ||
| 1980 | + tok->done = E_EOLS; | ||
| 1981 | + } | ||
| 1982 | + return ERRORTOKEN; | ||
| 1977 | 1983 | } | |
| 1978 | 1984 | } | |
| 1979 | 1985 | if (c == quote) { | |
| Back | FazBrowse Home | New Git URL |
0 commit comments