mirror of
https://github.com/github/codeql.git
synced 2025-12-16 16:53:25 +01:00
386 lines
14 KiB
Plaintext
386 lines
14 KiB
Plaintext
# State machine specification for unified Python tokenizer
|
|
# Handles all tokens for all versions of Python, including partial string tokens for handling f-strings.
|
|
# Stating transition table is "default" and starting state is "0"
|
|
#
|
|
#
|
|
|
|
|
|
#declarations
|
|
prefix_chars = 'u' or 'U' or 'b' or 'B' or 'r' or 'R'
|
|
one_to_nine = '1' or '2' or '3' or '4' or '5' or '6' or '7' or '8' or '9'
|
|
digits = '0' or one_to_nine
|
|
oct_digits = '0' or '1' or '2' or '3' or '4' or '5' or '6' or '7'
|
|
hex_digits = digits or 'a' or 'A' or 'b' or 'B' or 'c' or 'C' or 'd' or 'D' or 'e' or 'E' or 'f' or 'F'
|
|
feed = '\n' or '\r'
|
|
|
|
#tables
|
|
table default {
|
|
# 0 is starting state
|
|
0 -> whitespace_line for * do pushback;
|
|
|
|
#String prefix states
|
|
|
|
# When we encounter a prefix character, we are faced with the possibility
|
|
# that it is either the beginning of a string or of an identifier. With a
|
|
# single character of lookahead available, we therefore have to be in an
|
|
# intermediate state until we are able to determine which case we're in.
|
|
|
|
code -> maybe_string1 for prefix_chars do mark;
|
|
maybe_string1 -> maybe_string2 for prefix_chars
|
|
maybe_string1 or maybe_string2 -> quote_s for "'"
|
|
maybe_string1 or maybe_string2 -> quote_d for '"'
|
|
code -> quote_s for "'" do mark;
|
|
code -> quote_d for '"' do mark;
|
|
maybe_string1 or maybe_string2 -> in_identifier for * do pushback;
|
|
|
|
# In the following, `_s` means one single quote, `_ss` means two in a row,
|
|
# etc. Likewise `_d` indicates double quotes.
|
|
|
|
quote_s -> quote_ss for "'"
|
|
quote_d -> quote_dd for '"'
|
|
quote_s -> instring for * do pushback ; push(string_s);
|
|
quote_ss -> instring for "'" do push(string_sss);
|
|
quote_ss -> code for * do pushback ; emit(STRING);
|
|
quote_d -> instring for * do pushback ; push(string_d);
|
|
quote_dd -> instring for '"' do push(string_ddd);
|
|
quote_dd -> code for * do pushback ; emit(STRING);
|
|
|
|
#F-string prefix states
|
|
|
|
# The prefixes `u` and `b` are specific to Python 2, and f-strings are only
|
|
# valid for Python 3. Thus, the only potential prefixes are permutations of
|
|
# `f` and `fr` (upper/lowercase notwithstanding).
|
|
|
|
code -> maybe_fstring1 for 'f' or 'F' do mark;
|
|
maybe_string1 -> maybe_fstring2 for 'f' or 'F'
|
|
maybe_fstring1 -> maybe_fstring2 for 'r' or 'R'
|
|
maybe_fstring1 or maybe_fstring2 -> fquote_s for "'"
|
|
maybe_fstring1 or maybe_fstring2 -> fquote_d for '"'
|
|
maybe_fstring1 or maybe_fstring2 -> in_identifier for * do pushback;
|
|
fquote_s -> fquote_ss for "'"
|
|
fquote_d -> fquote_dd for '"'
|
|
fquote_s -> instring for * do pushback ; push(fstring_start_s);
|
|
fquote_ss -> instring for "'" do push(fstring_start_sss);
|
|
fquote_ss -> code for * do pushback ; emit(STRING);
|
|
fquote_d -> instring for * do pushback ; push(fstring_start_d);
|
|
fquote_dd -> instring for '"' do push(fstring_start_ddd);
|
|
fquote_dd -> code for * do pushback ; emit(STRING);
|
|
|
|
#String states
|
|
instring -> instring for *
|
|
instring -> unicode_or_escape for '\\'
|
|
unicode_or_escape -> unicode_or_raw for 'N'
|
|
unicode_or_raw -> unicode for '{'
|
|
unicode_or_raw -> instring for *
|
|
unicode -> instring for '}'
|
|
unicode -> unicode for *
|
|
unicode_or_escape -> escape for * do pushback;
|
|
|
|
escape -> instring for feed do newline;
|
|
escape -> instring for *
|
|
|
|
# When inside a parenthesized expression, newlines indicate the continuation
|
|
# of the expression, and not a return to a context where statements may
|
|
# appear. This is captured using the `paren` table.
|
|
|
|
code -> code for '(' do emit(LPAR, "("); push(paren);
|
|
code -> code for '[' do emit(LSQB, "["); push(paren);
|
|
code -> code for '{' do emit(LBRACE, "{"); push(paren);
|
|
code -> code for ')' do emit(RPAR, ")");
|
|
code -> code for ']' do emit(RSQB, "]");
|
|
code -> code for '}' do emit(RBRACE, "}");
|
|
code -> code for '`' do emit(BACKQUOTE, '`');
|
|
|
|
# Operators
|
|
code -> assign for '=' do mark;
|
|
code -> le for '<' do mark;
|
|
code -> ge for '>' do mark;
|
|
code -> bang for '!' do mark;
|
|
le -> binop for '<'
|
|
le -> code for '>' do emit(OP);
|
|
ge -> binop for '>'
|
|
bang or le or ge or assign -> code for '=' do emit(OP);
|
|
le or ge or assign -> code for * do pushback; emit(OP);
|
|
bang -> code for 'r' or 'a' or 's' or 'd' do emit(CONVERSION);
|
|
code -> colon for ':'
|
|
colon -> code for '=' do emit(COLONEQUAL, ":=");
|
|
colon -> code for * do pushback; emit(COLON, ":");
|
|
code -> code for ',' do emit(COMMA, ",");
|
|
code -> code for ';' do emit(SEMI, ";");
|
|
code -> at for '@' do mark;
|
|
at -> code for '=' do emit(OP);
|
|
at -> code for * do pushback; emit(AT, "@");
|
|
code -> dot for '.' do mark;
|
|
dot -> float for digits
|
|
dot -> code for * do pushback; emit(DOT, ".");
|
|
binop or slash or star or dash -> code for '=' do emit(OP);
|
|
binop or slash or star or dash -> code for * do pushback; emit(OP);
|
|
code -> star for '*' do mark;
|
|
star -> binop for '*'
|
|
code -> slash for '/' do mark;
|
|
slash -> binop for '/'
|
|
code -> dash for '-' do mark;
|
|
dash -> code for '>' do emit(RARROW);
|
|
code -> binop for '+' or '%' or '&' or '|' or '^' do mark;
|
|
code -> code for '~' do emit(OP, '~');
|
|
|
|
# Numeric literals
|
|
|
|
# Python admits a large variety of numeric literals, and the handling of
|
|
# various constructs is a bit inconsistent. For instance, prefixed zeroes are
|
|
# not allowed in front of integer numerals (unless all digits are between 0
|
|
# and 7, in which case it is treated as an octal number), but _are_ allowed if
|
|
# there is some other context that makes it a float or complex number. Thus,
|
|
# `09` is invalid, but `09.` and `09j` are valid. This means we have to be
|
|
# very careful in what we commit to in our tokenization, hence the rather
|
|
# complicated construction below.
|
|
|
|
code -> int for one_to_nine do mark;
|
|
int -> int for digits
|
|
zero or zero_int or binary or octal or int or hex -> code for 'l' or 'L' do emit(NUMBER);
|
|
int -> int_sep for '_'
|
|
int_sep -> int for digits
|
|
int_sep -> error for * do emit(ERRORTOKEN);
|
|
code -> zero for '0' do mark;
|
|
zero -> zero_int for digits
|
|
zero -> zero_int_sep for '_'
|
|
zero_int -> zero_int for digits
|
|
zero_int -> zero_int_sep for '_'
|
|
zero_int_sep -> zero_int for digits
|
|
zero_int_sep -> error for * do emit(ERRORTOKEN);
|
|
zero -> octal for 'o' or 'O'
|
|
octal -> octal for oct_digits
|
|
octal -> octal_sep for '_'
|
|
octal_sep -> octal for oct_digits
|
|
octal_sep -> error for * do emit(ERRORTOKEN);
|
|
zero or octal or hex or binary -> code for * do pushback; emit(NUMBER);
|
|
zero -> binary for 'b' or 'B'
|
|
binary -> binary for '0' or '1'
|
|
binary -> binary_sep for '_'
|
|
binary_sep -> binary for '0' or '1'
|
|
binary_sep -> error for * do emit(ERRORTOKEN);
|
|
zero -> hex for 'x' or 'X'
|
|
hex -> hex for hex_digits
|
|
hex -> hex_sep for '_'
|
|
hex_sep -> hex for hex_digits
|
|
hex_sep -> error for * do emit(ERRORTOKEN);
|
|
zero or zero_int or int -> int_dot for '.'
|
|
zero_int or int -> code for * do pushback; emit(NUMBER);
|
|
int_dot or float -> float for digits
|
|
float -> float_sep for '_'
|
|
float_sep -> float for digits
|
|
float_sep -> error for * do emit(ERRORTOKEN);
|
|
int_dot -> code for * do pushback; emit(NUMBER);
|
|
float or zero or zero_int or int or int_dot -> float_e for 'e'
|
|
float or zero or zero_int or int or int_dot -> float_E for 'E'
|
|
# `1 if 1else 0` is valid syntax, so we cannot assume 'e' always indicates a float.
|
|
float_e -> code for 'l' do pushback; pushback; emit(NUMBER);
|
|
float_e or float_E -> float_E for '+' or '-'
|
|
float_e or float_E or float_x -> float_x for digits
|
|
float_x -> float_x_sep for '_'
|
|
float_x_sep -> float_x for digits
|
|
float_x_sep -> error for * do emit(ERRORTOKEN);
|
|
float or float_x -> code for * do pushback; emit(NUMBER);
|
|
|
|
# Identifiers (e.g. names and keywords)
|
|
code -> in_identifier for IDENTIFIER do mark;
|
|
in_identifier -> in_identifier for IDENTIFIER or digits or IDENTIFIER_CONTINUE
|
|
code -> dollar_name for '$' do mark;
|
|
dollar_name -> dollar_name for IDENTIFIER or digits or IDENTIFIER_CONTINUE
|
|
code -> in_identifier for '_' do mark;
|
|
in_identifier -> in_identifier for '_'
|
|
in_identifier -> code for * do pushback; emit(NAME);
|
|
dollar_name -> code for * do pushback; emit(DOLLARNAME);
|
|
|
|
# Comments
|
|
code -> line_end_comment for '#' do mark;
|
|
line_end_comment -> code for feed do pushback; emit(COMMENT);
|
|
line_end_comment -> line_end_comment for *
|
|
comment -> whitespace_line for feed do pushback; emit(COMMENT);
|
|
comment -> comment for *
|
|
code -> whitespace_line for feed do emit(NEWLINE, "\n"); newline;
|
|
whitespace_line -> whitespace_line for SPACE or '\t' or '\f'
|
|
whitespace_line -> whitespace_line for feed do newline;
|
|
whitespace_line -> code for * do emit_indent;
|
|
whitespace_line -> comment for '#' do mark;
|
|
code -> code for SPACE or '\t'
|
|
|
|
# Line continuations and error states.
|
|
code or float_e or float_E -> error for * do emit(ERRORTOKEN);
|
|
code -> pending_continuation for '\\'
|
|
pending_continuation -> line_continuation for feed do newline;
|
|
line_continuation -> code for * do pushback; mark;
|
|
pending_continuation -> error for * do emit(ERRORTOKEN);
|
|
error -> code for * do pushback;
|
|
code -> code for * do mark; emit(ERRORTOKEN);
|
|
zero or int_dot or zero_int or int or float or float_x -> code for 'j' or 'J' do emit(NUMBER);
|
|
}
|
|
|
|
table paren(default) {
|
|
code -> code for feed do mark; newline;
|
|
code -> code for ')' do emit(RPAR, ")"); pop;
|
|
code -> code for ']' do emit(RSQB, "]"); pop;
|
|
code -> code for '}' do emit(RBRACE, "}"); pop;
|
|
}
|
|
|
|
#String starting with '
|
|
table string_s(default) {
|
|
instring -> code for "'" do pop; emit(STRING);
|
|
instring -> error for feed do pop; emit(ERRORTOKEN); newline;
|
|
}
|
|
|
|
#String starting with "
|
|
table string_d(default) {
|
|
instring -> code for '"' do pop; emit(STRING);
|
|
instring -> error for feed do pop; emit(ERRORTOKEN); newline;
|
|
}
|
|
|
|
#String starting with '''
|
|
table string_sss(default) {
|
|
instring -> string_x for "'"
|
|
instring -> instring for feed do newline;
|
|
string_x -> string_xx for "'"
|
|
string_x -> instring for feed do newline;
|
|
string_x -> instring for * do pushback;
|
|
string_xx -> code for "'" do pop; emit(STRING);
|
|
string_xx -> instring for feed do newline;
|
|
string_xx -> instring for * do pushback;
|
|
}
|
|
|
|
#String starting with """
|
|
table string_ddd(default) {
|
|
instring -> string_x for '"'
|
|
instring -> instring for feed do newline;
|
|
string_x -> string_xx for '"'
|
|
string_x -> instring for feed do newline;
|
|
string_x -> instring for * do pushback;
|
|
string_xx -> code for '"' do pop; emit(STRING);
|
|
string_xx -> instring for feed do newline;
|
|
string_xx -> instring for * do pushback;
|
|
}
|
|
|
|
#F-string part common to all fstrings
|
|
table fstring_sdsssddd(default) {
|
|
instring -> brace for '{'
|
|
|
|
escape -> brace for '{'
|
|
|
|
brace -> instring for '{'
|
|
brace -> code for * do pushback ; emit(FSTRING_MID); push(fstring_expr);
|
|
}
|
|
|
|
#F-string part common to ' and "
|
|
table fstring_sd(fstring_sdsssddd) {
|
|
instring -> error for feed do pop; emit(ERRORTOKEN); newline;
|
|
}
|
|
|
|
#F-string start for string starting with '
|
|
table fstring_start_s(fstring_sd) {
|
|
instring -> code for "'" do pop; emit(STRING);
|
|
|
|
# If this rule is removed or moved to a higher table, the QL tests start failing for unclear reasons.
|
|
# It's identical to a rule in default.
|
|
brace -> instring for '{'
|
|
brace -> code for * do pushback ; emit(FSTRING_START); pop; push(fstring_s); push(fstring_expr);
|
|
}
|
|
|
|
#F-string part for string starting with '
|
|
table fstring_s(fstring_sd) {
|
|
instring -> code for "'" do pop; emit(FSTRING_END);
|
|
}
|
|
|
|
#F-string start for string starting with "
|
|
table fstring_start_d(fstring_sd) {
|
|
instring -> code for '"' do pop; emit(STRING);
|
|
|
|
# If this rule is removed or moved to a higher table, the QL tests start failing for unclear reasons.
|
|
# It's identical to a rule in fstring_sdsssddd.
|
|
brace -> instring for '{'
|
|
brace -> code for * do pushback ; emit(FSTRING_START); pop; push(fstring_d); push(fstring_expr);
|
|
}
|
|
|
|
#F-string part for string starting with "
|
|
table fstring_d(fstring_sd) {
|
|
instring -> code for '"' do pop; emit(FSTRING_END);
|
|
}
|
|
|
|
#F-string part common to ''' and """
|
|
table fstring_sssddd(fstring_sdsssddd) {
|
|
instring -> instring for feed do newline;
|
|
|
|
string_x -> instring for feed do newline;
|
|
string_x -> instring for * do pushback;
|
|
|
|
string_xx -> instring for feed do newline;
|
|
string_xx -> instring for * do pushback;
|
|
}
|
|
|
|
#F-string start for string starting with '''
|
|
table fstring_start_sss(fstring_sssddd) {
|
|
instring -> string_x for "'"
|
|
|
|
string_x -> string_xx for "'"
|
|
|
|
string_xx -> code for "'" do pop; emit(STRING);
|
|
|
|
brace -> instring for '{'
|
|
brace -> code for * do pushback ; emit(FSTRING_START); pop; push(fstring_sss); push(fstring_expr);
|
|
}
|
|
|
|
#F-string part for string starting with '''
|
|
table fstring_sss(fstring_sssddd) {
|
|
instring -> string_x for "'"
|
|
|
|
string_x -> string_xx for "'"
|
|
|
|
string_xx -> code for "'" do pop; emit(FSTRING_END);
|
|
}
|
|
|
|
#F-string start for string starting with """
|
|
table fstring_start_ddd(fstring_sssddd) {
|
|
instring -> string_x for '"'
|
|
|
|
string_x -> string_xx for '"'
|
|
|
|
string_xx -> code for '"' do pop; emit(STRING);
|
|
|
|
brace -> instring for '{'
|
|
brace -> code for * do pushback ; emit(FSTRING_START); pop; push(fstring_ddd); push(fstring_expr);
|
|
}
|
|
|
|
#F-string part for string starting with """
|
|
table fstring_ddd(fstring_sssddd) {
|
|
instring -> string_x for '"'
|
|
|
|
string_x -> string_xx for '"'
|
|
|
|
string_xx -> code for '"' do pop; emit(FSTRING_END);
|
|
}
|
|
|
|
#Expression within an f-string
|
|
table fstring_expr(paren) {
|
|
code -> instring for '}' do pop; mark;
|
|
code -> instring for ':' do emit(COLON); push(format_specifier);
|
|
instring -> instring for '}' do pop; mark;
|
|
}
|
|
|
|
fspec_type = 'b' or 'c' or 'd' or 'e' or 'E' or 'f' or 'F' or 'g' or 'G' or 'n' or 'o' or 's' or 'x' or 'X' or '%'
|
|
fspec_align = '<' or '>' or '=' or '^'
|
|
fspec_sign = '+' or '-' or ' '
|
|
|
|
table format_specifier(default) {
|
|
instring -> code for '{' do emit(FSTRING_SPEC);
|
|
instring -> instring for '}' do pushback; emit(FSTRING_SPEC); pop;
|
|
|
|
code -> instring for '}' do mark;
|
|
}
|
|
|
|
|
|
|
|
#Special state for when dedents are pending.
|
|
table pending_dedent(default) {
|
|
code -> code for * do pop; emit_indent;
|
|
}
|
|
|
|
start: default
|