模組:PythonScript/Parser
外观
-- Module:PythonScript/Parser
local require_=require;local require=require_("Module:沙盒/a2569875/PythonScript/RequireWrapper")
-- 語法分析器
local Parser = {}
local Runtime = require("Module:PythonScript/Runtime")
local PARSE_DEPTH_LIMIT = 200
local current_parse_depth = 0
local BP = {
LOWEST = 0, ASSIGN = 10, COMMA = 12, WALRUS = 14, CONDITIONAL = 15,
OR = 20, AND = 30, NOT = 40, COMPARE = 50,
BIT_OR = 60, BIT_XOR = 70, BIT_AND = 80, SHIFT = 90,
SUM = 100, PRODUCT = 110, UNARY = 120, POWER = 130, CALL = 140,
}
local LBP = {
["="] = BP.ASSIGN, ["+="] = BP.ASSIGN, ["-="] = BP.ASSIGN, ["*="] = BP.ASSIGN, ["/="] = BP.ASSIGN, ["//="] = BP.ASSIGN, ["@="] = BP.ASSIGN,
["%="] = BP.ASSIGN, ["**="] = BP.ASSIGN, ["&="] = BP.ASSIGN, ["|="] = BP.ASSIGN, ["^="] = BP.ASSIGN, ["<<="] = BP.ASSIGN, [">>="] = BP.ASSIGN, ["\\="] = BP.ASSIGN,
[","] = BP.COMMA, [":="] = BP.WALRUS, ["if"] = BP.CONDITIONAL,
["or"] = BP.OR, ["and"] = BP.AND,
["=="] = BP.COMPARE, ["!="] = BP.COMPARE, ["<"] = BP.COMPARE, [">"] = BP.COMPARE, ["<="] = BP.COMPARE, [">="] = BP.COMPARE, ["is"] = BP.COMPARE, ["in"] = BP.COMPARE,
["is not"] = BP.COMPARE, ["not in"] = BP.COMPARE,
["|"] = BP.BIT_OR, ["^"] = BP.BIT_XOR, ["&"] = BP.BIT_AND,
["<<"] = BP.SHIFT, [">>"] = BP.SHIFT,
["+"] = BP.SUM, ["-"] = BP.SUM,
["*"] = BP.PRODUCT, ["/"] = BP.PRODUCT, ["//"] = BP.PRODUCT, ["%"] = BP.PRODUCT, ["@"] = BP.PRODUCT, ["\\"] = BP.PRODUCT,
["**"] = BP.POWER, ["("] = BP.CALL, ["["] = BP.CALL, ["."] = BP.CALL,
}
function Parser.parse(tokens, parse_mode)
local pos = 1
local current = tokens[pos]
local token_count = #tokens
local current_func_stack = {}
local goto_enabled = false
local function advance()
if pos < token_count then pos = pos + 1 end
current = tokens[pos]
end
local function match_type(type)
if current.type == type then advance(); return true end
return false
end
local function match_val(val)
if current.value == val then advance(); return true end
return false
end
local function consume_type(type, err_msg)
if current.type == type then
local t = current; advance(); return t
end
Runtime.Traceback.current_line = current.line
Runtime.raise("SyntaxError", err_msg or ("Expected " .. type))
end
local function consume_val(val, err_msg)
if current.value == val then
local t = current; advance(); return t
end
Runtime.Traceback.current_line = current.line
Runtime.raise("SyntaxError", err_msg or ("Expected '" .. val .. "'"))
end
local function skip_newlines()
while current.type == "NEWLINE" do advance() end
end
local parse_expression, parse_nud, parse_led
parse_expression = function(rbp)
current_parse_depth = current_parse_depth + 1
if current_parse_depth > PARSE_DEPTH_LIMIT then
Runtime.raise("SyntaxError", "maximum recursion depth exceeded during parsing")
end
rbp = rbp or BP.LOWEST
skip_newlines()
local left = parse_nud()
while current.type ~= "EOF" do
local next_bp = LBP[current.value] or BP.LOWEST
if current.value == "is" and tokens[pos+1] and tokens[pos+1].value == "not" then
next_bp = BP.COMPARE
elseif current.value == "not" and tokens[pos+1] and tokens[pos+1].value == "in" then
next_bp = BP.COMPARE
end
if rbp >= next_bp then break end
left = parse_led(left)
end
current_parse_depth = current_parse_depth - 1
return left
end
local function collect_comp_targets(node, target_set)
if not node then return end
if node.type == "Identifier" then
target_set[node.name] = true
elseif node.type == "Tuple" or node.type == "List" then
for _, el in ipairs(node.elements or {}) do collect_comp_targets(el, target_set) end
elseif node.type == "Starred" then
collect_comp_targets(node.value, target_set)
elseif node.type == "NamedExpr" then
Runtime.Traceback.current_line = node.line or current.line
Runtime.raise("SyntaxError", "cannot assign to named expression")
end
end
local function check_comp_walrus(node, target_set, in_iter)
if type(node) ~= "table" then return end
if node.type == "NamedExpr" then
if in_iter then
Runtime.Traceback.current_line = node.line or current.line
Runtime.raise("SyntaxError", "assignment expression cannot be used in a comprehension iterable expression")
end
if node.target and target_set[node.target.name] then
Runtime.Traceback.current_line = node.line or current.line
Runtime.raise("SyntaxError", "assignment expression cannot rebind comprehension iteration variable '" .. tostring(node.target.name) .. "'")
end
end
for k, v in pairs(node) do
if k ~= "type" and k ~= "line" and type(v) == "table" then
check_comp_walrus(v, target_set, in_iter)
end
end
end
local function parse_comprehension_generators(elt1, elt2)
local generators = {}
local target_set = {}
while true do
local old_in_bp = LBP["in"]; local old_if_bp = LBP["if"]
LBP["in"] = -1
local targets = { parse_expression(BP.COMMA) }
while match_val(",") do table.insert(targets, parse_expression(BP.COMMA)) end
LBP["in"] = old_in_bp
local target = #targets == 1 and targets[1] or { type = "Tuple", elements = targets, line = current.line }
collect_comp_targets(target, target_set)
consume_val("in", "Expected 'in' in comprehension")
LBP["if"] = -1; local iter = parse_expression(BP.CONDITIONAL)
local ifs = {}
while match_val("if") do
LBP["if"] = old_if_bp
table.insert(ifs, parse_expression(BP.WALRUS - 1))
LBP["if"] = -1
end
LBP["if"] = old_if_bp
table.insert(generators, { target = target, iter = iter, ifs = ifs })
if not match_val("for") then break end
end
for _, gen in ipairs(generators) do
check_comp_walrus(gen.iter, target_set, true)
for _, if_expr in ipairs(gen.ifs) do check_comp_walrus(if_expr, target_set, false) end
end
if elt1 then check_comp_walrus(elt1, target_set, false) end
if elt2 then check_comp_walrus(elt2, target_set, false) end
return generators
end
local function parse_parameters(end_val, allow_annotations)
local args, kwonlyargs = {}, {}
local defaults, annotations = {}, {}
local posonly_count = 0
local vararg, kwarg = nil, nil
local seen_names = {}
local seen_slash, seen_star, need_kwonly = false, false, false
local seen_pos_default = false
local function reg_name(name, line_no)
if seen_names[name] then
Runtime.Traceback.current_line = line_no or current.line
Runtime.raise("SyntaxError", "duplicate argument '" .. name .. "' in function definition")
end
seen_names[name] = true
end
if current.value ~= end_val then
while true do
if kwarg ~= nil then
Runtime.Traceback.current_line = current.line
Runtime.raise("SyntaxError", "arguments cannot follow var-keyword argument")
end
if match_val("/") then
if seen_slash then
Runtime.Traceback.current_line = current.line
Runtime.raise("SyntaxError", "/ may appear only once")
end
if seen_star then
Runtime.Traceback.current_line = current.line
Runtime.raise("SyntaxError", "/ must be ahead of *")
end
if #args == 0 then
Runtime.Traceback.current_line = current.line
Runtime.raise("SyntaxError", "at least one argument must precede /")
end
seen_slash = true
posonly_count = #args
elseif match_val("*") then
if seen_star then
Runtime.Traceback.current_line = current.line
Runtime.raise("SyntaxError", "* argument may appear only once")
end
seen_star = true
if current.type == "IDENTIFIER" then
local tok = consume_type("IDENTIFIER")
vararg = tok.value
reg_name(vararg, tok.line)
if allow_annotations and match_val(":") then
annotations[vararg] = parse_expression(BP.COMMA)
end
if match_val("=") then
Runtime.Traceback.current_line = current.line
Runtime.raise("SyntaxError", "var-positional argument cannot have default value")
end
else
need_kwonly = true
end
elseif match_val("**") then
if need_kwonly then
Runtime.Traceback.current_line = current.line
Runtime.raise("SyntaxError", "named arguments must follow bare *")
end
local tok = consume_type("IDENTIFIER", "Expected parameter name after '**'")
kwarg = tok.value
reg_name(kwarg, tok.line)
if allow_annotations and match_val(":") then
annotations[kwarg] = parse_expression(BP.COMMA)
end
if match_val("=") then
Runtime.Traceback.current_line = current.line
Runtime.raise("SyntaxError", "var-keyword argument cannot have default value")
end
else
local tok = consume_type("IDENTIFIER", "Expected parameter name")
local arg_name = tok.value
reg_name(arg_name, tok.line)
if allow_annotations and match_val(":") then
annotations[arg_name] = parse_expression(BP.COMMA)
end
local has_default = false
if match_val("=") then
defaults[arg_name] = parse_expression(BP.COMMA)
has_default = true
end
if seen_star then
need_kwonly = false
table.insert(kwonlyargs, arg_name)
else
if has_default then
seen_pos_default = true
elseif seen_pos_default then
Runtime.Traceback.current_line = tok.line
Runtime.raise("SyntaxError", "non-default argument follows default argument")
end
table.insert(args, arg_name)
end
end
if not match_val(",") then break end
if current.value == end_val then break end
end
end
if need_kwonly then
Runtime.Traceback.current_line = current.line
Runtime.raise("SyntaxError", "named arguments must follow bare *")
end
return {
args = args,
posonly_count = posonly_count,
kwonlyargs = kwonlyargs,
defaults = defaults,
annotations = annotations,
vararg = vararg,
kwarg = kwarg
}
end
parse_nud = function()
local t = current
advance()
if t.type == "NUMBER" then return { type = "Number", value = t.value, is_complex = t.is_complex, line = t.line }
elseif t.type == "IDENTIFIER" then return { type = "Identifier", name = t.value, line = t.line }
elseif t.type == "KEYWORD" then
if t.value == "True" then return { type = "Boolean", value = true, line = t.line }
elseif t.value == "False" then return { type = "Boolean", value = false, line = t.line }
elseif t.value == "None" then return { type = "None", line = t.line }
elseif t.value == "not" then return { type = "UnaryOp", op = "not", right = parse_expression(BP.NOT), line = t.line }
elseif t.value == "await" then
if #current_func_stack > 0 and current_func_stack[#current_func_stack].is_async then
return { type = "Await", value = parse_expression(BP.UNARY), line = t.line }
else
Runtime.Traceback.current_line = t.line
Runtime.raise("SyntaxError", "'await' outside async function")
end
elseif t.value == "yield" then
if #current_func_stack > 0 then
current_func_stack[#current_func_stack].is_generator = true
else
if parse_mode ~= "invoke" then
Runtime.Traceback.current_line = t.line
Runtime.raise("SyntaxError", "'yield' outside function")
end
end
if current.type == "KEYWORD" and current.value == "from" then
advance()
return { type = "YieldFrom", value = parse_expression(), line = t.line }
end
local val = nil
if current.type ~= "NEWLINE" and current.type ~= "EOF" and current.value ~= ")" and current.value ~= "]" and current.value ~= "}" and current.value ~= "," then
val = parse_expression()
end
return { type = "Yield", value = val, line = t.line }
elseif t.value == "lambda" then
local params = parse_parameters(":", false)
consume_val(":", "Expected ':' after lambda arguments")
table.insert(current_func_stack, { is_generator = false, is_async = false })
local body = parse_expression(BP.COMMA)
local func_info = table.remove(current_func_stack)
return {
type = "Lambda",
args = params.args,
posonly_count = params.posonly_count,
kwonlyargs = params.kwonlyargs,
defaults = params.defaults,
vararg = params.vararg,
kwarg = params.kwarg,
body = body,
is_generator = func_info.is_generator,
line = t.line
}
end
elseif t.type == "STRING" then
local str_node = { type = "String", value = t.value, is_fstring = t.is_fstring, is_raw = t.is_raw, is_bytes = t.is_bytes, is_wiki = t.is_wiki, line = t.line }
while current.type == "STRING" do
str_node.value = str_node.value .. current.value
str_node.is_fstring = str_node.is_fstring or current.is_fstring
str_node.is_bytes = str_node.is_bytes or current.is_bytes
str_node.is_wiki = str_node.is_wiki or current.is_wiki
advance()
end
return str_node
elseif t.value == "(" then
if current.value == ")" then advance(); return { type = "Tuple", elements = {}, line = t.line } end
local expr = parse_expression()
if match_val("for") then
local generators = parse_comprehension_generators(expr)
consume_val(")", "Expected ')' to close generator expression")
return { type = "GeneratorExp", elt = expr, generators = generators, line = t.line }
end
consume_val(")", "Expected ')' to close parentheses")
if type(expr) == "table" then expr.parenthesized = true end
return expr
elseif t.value == "[" then
if match_val("]") then return { type = "List", elements = {}, line = t.line } end
local first_expr = parse_expression(BP.COMMA)
if match_val("for") then
local generators = parse_comprehension_generators(first_expr)
consume_val("]", "Expected ']' for list comprehension")
return { type = "ListComp", elt = first_expr, generators = generators, line = t.line }
else
local elements = { first_expr }
while match_val(",") do
if current.value == "]" then break end
table.insert(elements, parse_expression(BP.COMMA))
end
consume_val("]", "Expected ']' for list")
return { type = "List", elements = elements, line = t.line }
end
elseif t.value == "{" then
if match_val("}") then return { type = "Dict", pairs = {}, line = t.line } end
if match_val("**") then
local rest_expr = parse_expression(BP.COMMA)
local kv_pairs = { { key = nil, value = rest_expr, line = current.line } }
while match_val(",") do
if current.value == "}" then break end
if match_val("**") then
table.insert(kv_pairs, { key = nil, value = parse_expression(BP.COMMA), line = current.line })
else
local k = parse_expression(BP.COMMA); consume_val(":")
local v = parse_expression(BP.COMMA)
table.insert(kv_pairs, { key = k, value = v })
end
end
consume_val("}", "Expected '}' for dict")
return { type = "Dict", pairs = kv_pairs, line = t.line }
end
local first_expr = parse_expression(BP.COMMA)
if match_val(":") then
local val_expr = parse_expression(BP.COMMA)
if match_val("for") then
local generators = parse_comprehension_generators(first_expr, val_expr)
consume_val("}", "Expected '}' for dict comprehension")
return { type = "DictComp", key = first_expr, value = val_expr, generators = generators, line = t.line }
else
local kv_pairs = { { key = first_expr, value = val_expr } }
while match_val(",") do
if current.value == "}" then break end
if match_val("**") then
table.insert(kv_pairs, { key = nil, value = parse_expression(BP.COMMA), line = current.line })
else
local k = parse_expression(BP.COMMA); consume_val(":")
local v = parse_expression(BP.COMMA)
table.insert(kv_pairs, { key = k, value = v })
end
end
consume_val("}", "Expected '}' for dict")
return { type = "Dict", pairs = kv_pairs, line = t.line }
end
else
if match_val("for") then
local generators = parse_comprehension_generators(first_expr)
consume_val("}", "Expected '}' for set comprehension")
return { type = "SetComp", elt = first_expr, generators = generators, line = t.line }
else
local elements = { first_expr }
while match_val(",") do
if current.value == "}" then break end
table.insert(elements, parse_expression(BP.COMMA))
end
consume_val("}", "Expected '}' for set")
return { type = "Set", elements = elements, line = t.line }
end
end
elseif t.value == "+" or t.value == "-" or t.value == "~" then return { type = "UnaryOp", op = t.value, right = parse_expression(BP.UNARY), line = t.line }
elseif t.value == "*" then return { type = "Starred", value = parse_expression(BP.UNARY), line = t.line }
end
Runtime.Traceback.current_line = t.line
Runtime.raise("SyntaxError", "Unexpected token '" .. t.value .. "'")
end
parse_led = function(left)
local t = current
advance()
if t.value == "is" and current.value == "not" then
advance()
t = { type = "KEYWORD", value = "is not", line = t.line }
elseif t.value == "not" and current.value == "in" then
advance()
t = { type = "KEYWORD", value = "not in", line = t.line }
end
local bp = LBP[t.value] or BP.LOWEST
if t.value == "," then
local elements = { left }
if current.type ~= "NEWLINE" and current.type ~= "EOF" and current.value ~= "=" and current.value ~= ")" and current.value ~= "]" and current.value ~= "}" then
table.insert(elements, parse_expression(BP.COMMA))
while current.value == "," do
advance()
if current.type == "NEWLINE" or current.type == "EOF" or current.value == "=" or current.value == ")" or current.value == "]" or current.value == "}" then break end
table.insert(elements, parse_expression(BP.COMMA))
end
end
return { type = "Tuple", elements = elements, line = t.line }
elseif t.value == "(" then
local args = {}; local kwargs = {}
local seen_keyword = false
local seen_kw_unpack = false
local seen_kw_keys = {}
if current.value ~= ")" then
while true do
if match_val("*") then
if seen_kw_unpack then
Runtime.Traceback.current_line = current.line
Runtime.raise("SyntaxError", "iterable argument unpacking follows keyword argument unpacking")
end
table.insert(args, { type = "Starred", value = parse_expression(BP.COMMA) })
elseif match_val("**") then
seen_kw_unpack = true
table.insert(kwargs, { key = nil, value = parse_expression(BP.COMMA) })
elseif current.type == "IDENTIFIER" and tokens[pos+1] and tokens[pos+1].value == "=" then
local key_tok = consume_type("IDENTIFIER")
local key = key_tok.value
consume_val("=")
if seen_kw_keys[key] then
Runtime.Traceback.current_line = key_tok.line
Runtime.raise("SyntaxError", "keyword argument repeated: " .. key)
end
seen_kw_keys[key] = true
seen_keyword = true
table.insert(kwargs, { key = key, value = parse_expression(BP.COMMA) })
else
local arg_line = current.line
local arg_expr = parse_expression(BP.COMMA)
if current.value == "=" then
Runtime.Traceback.current_line = arg_line
Runtime.raise("SyntaxError", "expression cannot contain assignment, perhaps you meant \"==\"?")
end
if seen_kw_unpack then
Runtime.Traceback.current_line = arg_line
Runtime.raise("SyntaxError", "positional argument follows keyword argument unpacking")
elseif seen_keyword then
Runtime.Traceback.current_line = arg_line
Runtime.raise("SyntaxError", "positional argument follows keyword argument")
end
if match_val("for") then
local generators = parse_comprehension_generators(arg_expr)
table.insert(args, { type = "GeneratorExp", elt = arg_expr, generators = generators, line = t.line })
else
table.insert(args, arg_expr)
end
end
if not match_val(",") then break end
if current.value == ")" then break end
end
end
consume_val(")", "Expected ')' after function arguments")
return { type = "Call", callee = left, args = args, kwargs = kwargs, line = t.line }
elseif t.value == "[" then
local function parse_subscript_item()
local is_slice = false; local lower, upper, step = nil, nil, nil
if match_val(":") then
is_slice = true
if current.value ~= "]" and current.value ~= ":" and current.value ~= "," then upper = parse_expression(BP.COMMA) end
if match_val(":") then if current.value ~= "]" and current.value ~= "," then step = parse_expression(BP.COMMA) end end
else
lower = parse_expression(BP.COMMA)
if match_val(":") then
is_slice = true
if current.value ~= "]" and current.value ~= ":" and current.value ~= "," then upper = parse_expression(BP.COMMA) end
if match_val(":") then if current.value ~= "]" and current.value ~= "," then step = parse_expression(BP.COMMA) end end
end
end
if is_slice then return { type = "Slice", lower = lower, upper = upper, step = step, line = t.line }
else return lower end
end
local first_item = parse_subscript_item()
local index_node
if match_val(",") then
local elts = { first_item }
while current.value ~= "]" and current.type ~= "EOF" do
table.insert(elts, parse_subscript_item())
if not match_val(",") then break end
end
index_node = { type = "Tuple", elements = elts, line = t.line }
else
index_node = first_item
end
consume_val("]", "Expected ']' after index or slice")
return { type = "Index", target = left, index = index_node, line = t.line }
elseif t.value == "if" then
local test = parse_expression(BP.CONDITIONAL)
consume_val("else", "Expected 'else' in conditional expression")
local orelse = parse_expression(bp - 1)
return { type = "IfExp", body = left, test = test, orelse = orelse, line = t.line }
elseif t.value == "." then
local attr = consume_type("IDENTIFIER", "Expected attribute name").value
return { type = "Attribute", target = left, attr = attr, line = t.line }
elseif t.value == "as" then
local alias = consume_type("IDENTIFIER", "Expected identifier after 'as'").value
if alias == "_" then
Runtime.Traceback.current_line = t.line
Runtime.raise("SyntaxError", "cannot use '_' as a target of 'as' in match pattern")
end
return { type = "MatchAs", pattern = left, alias = alias, line = t.line }
elseif t.value == ":=" then
if left.type ~= "Identifier" or left.parenthesized then
Runtime.Traceback.current_line = t.line
Runtime.raise("SyntaxError", "cannot use assignment expressions with " .. string.lower(left.type))
end
return { type = "NamedExpr", target = left, value = parse_expression(bp - 1), line = t.line }
elseif bp == BP.ASSIGN then
if left.type == "NamedExpr" then
Runtime.Traceback.current_line = t.line
Runtime.raise("SyntaxError", "cannot assign to named expression")
end
local val_expr = parse_expression(bp - 1)
if val_expr.type == "NamedExpr" and not val_expr.parenthesized then
Runtime.Traceback.current_line = val_expr.line or t.line
Runtime.raise("SyntaxError", "unparenthesized named expression cannot be used right of '" .. t.value .. "'")
end
return { type = "Assign", op = t.value, target = left, value = val_expr, line = t.line }
elseif t.value == "**" then return { type = "BinaryOp", op = t.value, left = left, right = parse_expression(bp - 1), line = t.line }
else return { type = "BinaryOp", op = t.value, left = left, right = parse_expression(bp), line = t.line }
end
end
local parse_statement, parse_block, parse_suite
parse_block = function()
consume_type("INDENT", "Expected an indented block")
local stmts = {}
while current.type ~= "DEDENT" and current.type ~= "EOF" do table.insert(stmts, parse_statement()) end
consume_type("DEDENT", "Expected dedent")
return stmts
end
parse_suite = function()
if current.type == "NEWLINE" then
advance()
return parse_block()
else
return { parse_statement() }
end
end
local function parse_import_name()
local name_parts = {}
while current.value == "." do
table.insert(name_parts, ".")
advance()
end
if current.type == "IDENTIFIER" then
table.insert(name_parts, current.value)
advance()
elseif #name_parts == 0 then
Runtime.Traceback.current_line = current.line
Runtime.raise("SyntaxError", "Expected module name")
end
while current.value == "." or current.value == ":" or current.value == "/" or current.value == "-" do
table.insert(name_parts, current.value)
advance()
if current.type == "IDENTIFIER" or current.type == "NUMBER" then
table.insert(name_parts, tostring(current.value))
advance()
end
end
return table.concat(name_parts)
end
parse_statement = function()
skip_newlines()
local decorator_list = {}
while current.value == "@" do
advance()
table.insert(decorator_list, parse_expression())
consume_type("NEWLINE", "Expected newline after decorator")
skip_newlines()
end
local is_async = false
if current.type == "KEYWORD" and current.value == "async" then
is_async = true
advance()
end
local t = current
local stmt = nil
if t.type == "KEYWORD" then
if t.value == "if" then
advance()
local test = parse_expression(); consume_val(":")
local body = parse_suite(); local orelse = {}
while current.value == "elif" do
advance()
local elif_test = parse_expression(); consume_val(":")
table.insert(orelse, { type = "If", test = elif_test, body = parse_suite(), orelse = {} })
end
if match_val("else") then
consume_val(":")
local else_body = parse_suite()
if #orelse > 0 then orelse[#orelse].orelse = else_body else orelse = else_body end
end
stmt = { type = "If", test = test, body = body, orelse = orelse, line = t.line }
elseif t.value == "while" then
advance()
local test = parse_expression(); consume_val(":")
local body = parse_suite(); local orelse = {}
if match_val("else") then consume_val(":"); orelse = parse_suite() end
stmt = { type = "While", test = test, body = body, orelse = orelse, line = t.line }
elseif t.value == "for" then
advance()
local old_in_bp = LBP["in"]; LBP["in"] = -1
local targets = { parse_expression(BP.COMMA) }
while match_val(",") do table.insert(targets, parse_expression(BP.COMMA)) end
LBP["in"] = old_in_bp
local target = #targets == 1 and targets[1] or { type = "Tuple", elements = targets, line = t.line }
consume_val("in", "Expected 'in' after for loop target")
local iter = parse_expression(); consume_val(":")
local body = parse_suite(); local orelse = {}
if match_val("else") then consume_val(":"); orelse = parse_suite() end
stmt = { type = "For", target = target, iter = iter, body = body, orelse = orelse, line = t.line }
elseif t.value == "try" then
advance(); consume_val(":")
local body = parse_suite(); local handlers = {}
while current.value == "except" do
advance(); local type_expr = nil; local name = nil
if current.value ~= ":" then
type_expr = parse_expression()
if match_val("as") then name = consume_type("IDENTIFIER").value end
end
consume_val(":")
table.insert(handlers, { type = "ExceptHandler", type_expr = type_expr, name = name, body = parse_suite() })
end
local orelse = {}
if match_val("else") then consume_val(":"); orelse = parse_suite() end
local finalbody = {}
if match_val("finally") then consume_val(":"); finalbody = parse_suite() end
stmt = { type = "Try", body = body, handlers = handlers, orelse = orelse, finalbody = finalbody, line = t.line }
elseif t.value == "def" then
advance()
local name = consume_type("IDENTIFIER", "Expected function name").value
consume_val("(")
local params = parse_parameters(")", true)
consume_val(")")
local returns = nil
if match_val("->") or (current.value == "-" and tokens[pos+1] and tokens[pos+1].value == ">") then
if current.value == "-" then advance(); advance() end
returns = parse_expression(BP.COMMA)
end
consume_val(":")
table.insert(current_func_stack, { is_generator = false, is_async = is_async })
local body = parse_suite()
local func_info = table.remove(current_func_stack)
stmt = {
type = "FunctionDef",
name = name,
args = params.args,
posonly_count = params.posonly_count,
kwonlyargs = params.kwonlyargs,
defaults = params.defaults,
annotations = params.annotations,
returns = returns,
vararg = params.vararg,
kwarg = params.kwarg,
body = body,
decorator_list = decorator_list,
is_generator = func_info.is_generator,
is_async = func_info.is_async,
line = t.line
}
elseif t.value == "class" then
advance()
local name = consume_type("IDENTIFIER", "Expected class name").value
local bases = {}
local kwargs = {}
if match_val("(") then
local seen_keyword = false
local seen_kw_unpack = false
local seen_kw_keys = {}
if current.value ~= ")" then
while true do
if match_val("*") then
if seen_kw_unpack then
Runtime.Traceback.current_line = current.line
Runtime.raise("SyntaxError", "iterable argument unpacking follows keyword argument unpacking")
end
table.insert(bases, { type = "Starred", value = parse_expression(BP.COMMA) })
elseif match_val("**") then
seen_kw_unpack = true
table.insert(kwargs, { key = nil, value = parse_expression(BP.COMMA) })
elseif current.type == "IDENTIFIER" and tokens[pos+1] and tokens[pos+1].value == "=" then
local key_tok = consume_type("IDENTIFIER")
local key = key_tok.value
consume_val("=")
if seen_kw_keys[key] then
Runtime.Traceback.current_line = key_tok.line
Runtime.raise("SyntaxError", "keyword argument repeated: " .. key)
end
seen_kw_keys[key] = true
seen_keyword = true
table.insert(kwargs, { key = key, value = parse_expression(BP.COMMA) })
else
if seen_kw_unpack then
Runtime.Traceback.current_line = current.line
Runtime.raise("SyntaxError", "positional argument follows keyword argument unpacking")
elseif seen_keyword then
Runtime.Traceback.current_line = current.line
Runtime.raise("SyntaxError", "positional argument follows keyword argument")
end
table.insert(bases, parse_expression(BP.COMMA))
end
if not match_val(",") then break end
if current.value == ")" then break end
end
end
consume_val(")")
end
consume_val(":")
stmt = { type = "ClassDef", name = name, bases = bases, kwargs = kwargs, body = parse_suite(), decorator_list = decorator_list, line = t.line }
-- (import, from, global, nonlocal, assert, del, raise 保持原樣,直接跳到 with)
elseif t.value == "import" then
advance()
local names = {}
while true do
local name = parse_import_name(); local alias = nil
if match_val("as") then alias = consume_type("IDENTIFIER").value end
if name == "goto" then goto_enabled = true end
table.insert(names, { name = name, alias = alias })
if not match_val(",") then break end
end
stmt = { type = "Import", names = names, line = t.line }
elseif t.value == "from" then
advance()
local module = parse_import_name(); consume_val("import")
if module == "goto" then goto_enabled = true end
local names = {}
if current.value ~= "NEWLINE" and current.type ~= "NEWLINE" and current.type ~= "EOF" then
if current.value == "*" then
table.insert(names, { name = "*" })
advance()
else
local has_parens = match_val("(") -- 💡 偵測是否使用括號
-- 支援 from module import () 的空括號防呆
if has_parens and current.value == ")" then
-- 空的 import 列表
else
while true do
local item = consume_type("IDENTIFIER").value; local alias = nil
if match_val("as") then alias = consume_type("IDENTIFIER").value end
table.insert(names, { name = item, alias = alias })
if not match_val(",") then break end
-- 💡 若有括號且遇到結尾逗號,提早結束迴圈
if has_parens and current.value == ")" then break end
end
end
if has_parens then
consume_val(")", "Expected ')' to close import list") -- 💡 消耗掉右括號
end
end
end
stmt = { type = "FromImport", module = module, names = names, line = t.line }
elseif t.value == "global" or t.value == "nonlocal" then
local kw_type = (t.value == "global") and "Global" or "Nonlocal"
advance()
local names = { consume_type("IDENTIFIER", "Expected identifier").value }
while match_val(",") do table.insert(names, consume_type("IDENTIFIER").value) end
stmt = { type = kw_type, names = names, line = t.line }
elseif t.value == "assert" then
advance()
local test = parse_expression(); local msg = nil
if match_val(",") then msg = parse_expression() end
stmt = { type = "Assert", test = test, msg = msg, line = t.line }
elseif t.value == "del" then
advance()
local targets = { parse_expression(BP.COMMA) }
while match_val(",") do table.insert(targets, parse_expression(BP.COMMA)) end
stmt = { type = "Delete", targets = targets, line = t.line }
elseif t.value == "raise" then
advance()
local exc = nil; local cause = nil
if current.type ~= "NEWLINE" and current.type ~= "EOF" then
exc = parse_expression()
if match_val("from") then cause = parse_expression() end
end
stmt = { type = "Raise", exc = exc, cause = cause, line = t.line }
elseif t.value == "with" then
advance()
local items = {}
while true do
local context_expr = parse_expression(BP.COMMA); local optional_vars = nil
if match_val("as") then optional_vars = parse_expression(BP.COMMA) end
table.insert(items, { context_expr = context_expr, optional_vars = optional_vars })
if not match_val(",") then break end
end
consume_val(":")
stmt = { type = "With", items = items, body = parse_suite(), line = t.line }
elseif t.value == "return" then
advance()
if #current_func_stack == 0 and parse_mode ~= "invoke" then
Runtime.Traceback.current_line = t.line
Runtime.raise("SyntaxError", "'return' outside function")
end
local val = nil
if current.type ~= "NEWLINE" and current.type ~= "EOF" then val = parse_expression() end
stmt = { type = "Return", value = val, line = t.line }
elseif t.value == "break" then advance(); stmt = { type = "Break", line = t.line }
elseif t.value == "continue" then advance(); stmt = { type = "Continue", line = t.line }
elseif t.value == "pass" then advance(); stmt = { type = "Pass", line = t.line }
end
elseif t.type == "IDENTIFIER" and t.value == "match" and tokens[pos+1] and tokens[pos+1].value ~= "=" and tokens[pos+1].value ~= "(" and tokens[pos+1].value ~= "." then
local function validate_match_pattern(pat)
if type(pat) ~= "table" then return end
if pat.type == "Dict" then
local seen_star = false
for i, p in ipairs(pat.pairs or {}) do
if p.key == nil then
if seen_star or i < #pat.pairs then
Runtime.Traceback.current_line = p.line or pat.line or current.line
Runtime.raise("SyntaxError", "double star pattern must be the last entry in a mapping pattern")
end
if type(p.value) ~= "table" or p.value.type ~= "Identifier" or p.value.name == "_" then
Runtime.Traceback.current_line = p.line or pat.line or current.line
Runtime.raise("SyntaxError", "cannot use '_' or non-identifier as a target of '**' in mapping pattern")
end
seen_star = true
else
validate_match_pattern(p.value)
end
end
elseif pat.type == "Tuple" or pat.type == "List" then
for _, el in ipairs(pat.elements or {}) do validate_match_pattern(el) end
elseif pat.type == "Call" then
for _, a in ipairs(pat.args or {}) do validate_match_pattern(a) end
for _, kw in ipairs(pat.kwargs or {}) do validate_match_pattern(kw.value) end
elseif pat.type == "BinaryOp" and pat.op == "|" then
validate_match_pattern(pat.left)
validate_match_pattern(pat.right)
elseif pat.type == "MatchAs" then
validate_match_pattern(pat.pattern)
end
end
advance()
local subject = parse_expression()
consume_val(":")
consume_type("NEWLINE")
consume_type("INDENT")
local cases = {}
while current.type == "IDENTIFIER" and current.value == "case" do
advance()
local old_if_bp = LBP["if"]
local old_as_bp = LBP["as"]
LBP["if"] = -1
LBP["as"] = 18
local pattern = parse_expression()
LBP["if"] = old_if_bp
LBP["as"] = old_as_bp
validate_match_pattern(pattern)
local guard = nil
if match_val("if") then guard = parse_expression() end
consume_val(":")
local body = parse_suite()
table.insert(cases, { pattern = pattern, guard = guard, body = body })
end
consume_type("DEDENT")
stmt = { type = "Match", subject = subject, cases = cases, line = t.line }
elseif t.type == "IDENTIFIER" and (t.value == "goto" or t.value == "label") and tokens[pos+1] and tokens[pos+1].type == "IDENTIFIER" then
if not goto_enabled then
Runtime.Traceback.current_line = t.line
Runtime.raise("SyntaxError", "invalid syntax (goto/label requires 'import goto')")
end
local keyword = t.value
advance() -- 消耗 goto/label
local target_name = consume_type("IDENTIFIER", "Expected label name after " .. keyword).value
stmt = { type = (keyword == "goto" and "Goto" or "Label"), target = target_name, line = t.line }
end
if not stmt then
local expr = parse_expression()
if current.value == ":" then
if expr.type == "NamedExpr" then
Runtime.Traceback.current_line = expr.line or current.line
Runtime.raise("SyntaxError", "cannot assign to named expression")
end
advance() -- 消耗 ':'
-- 【關鍵修復】:傳入 BP.ASSIGN,強制 Parser 在遇到 '=' 時停止解析,避免吃掉預設值
local annotation = parse_expression(BP.ASSIGN)
local value = nil
if current.value == "=" then
advance() -- 消耗 '='
value = parse_expression()
end
stmt = { type = "AnnAssign", target = expr, annotation = annotation, value = value, line = t.line }
else
if expr.type == "NamedExpr" and not expr.parenthesized then
Runtime.Traceback.current_line = expr.line or current.line
Runtime.raise("SyntaxError", "cannot use assignment expression here (use ':=' inside parentheses or '=' for assignment)")
end
stmt = { type = "ExprStatement", expr = expr, line = t.line }
end
end
if current.type == "NEWLINE" then advance() end
return stmt
end
if parse_mode == "eval" then
skip_newlines()
if current.type == "EOF" then
Runtime.Traceback.current_line = current.line
Runtime.raise("SyntaxError", "unexpected EOF while parsing")
end
local expr = parse_expression()
if expr.type == "Assign" or expr.type == "Yield" or expr.type == "YieldFrom" then
Runtime.Traceback.current_line = expr.line or current.line
Runtime.raise("SyntaxError", "invalid syntax")
end
skip_newlines()
if current.type ~= "EOF" then
Runtime.Traceback.current_line = current.line
Runtime.raise("SyntaxError", "invalid syntax")
end
return { type = "Expression", body = expr }
end
local ast = { type = "Module", body = {} }
while current.type ~= "EOF" do
skip_newlines()
if current.type == "EOF" then break end
table.insert(ast.body, parse_statement())
end
return ast
end
return Parser