// See also: file:///usr/share/doc/python/html/reference/grammar.html?highlight=grammar
// See also: https://github.com/antlr/grammars-v4/blob/master/python3/Python3.g4
// See also: file:///usr/share/doc/python/html/reference/compound_stmts.html#function-definitions
// See also: https://greentreesnakes.readthedocs.io/en/latest/nodes.html#keyword
use num_bigint::BigInt;
use ruff_text_size::TextSize;
use ruff_python_ast::{self as ast, Ranged, IpyEscapeKind};
use crate::{
Mode,
lexer::{LexicalError, LexicalErrorType},
function::{ArgumentList, parse_arguments, validate_pos_params, validate_arguments},
context::set_context,
string::parse_strings,
token::{self, StringKind},
};
use lalrpop_util::ParseError;
grammar(mode: Mode);
// This is a hack to reduce the amount of lalrpop tables generated:
// For each public entry point, a full parse table is generated.
// By having only a single pub function, we reduce this to one.
pub(crate) Top: ast::Mod = {
StartModule => ast::ModModule { body, range: (start..end).into() }.into(),
StartExpression ("\n")* => ast::ModExpression { body: Box::new(body), range: (start..end).into() }.into()
};
Program: ast::Suite = {
=> vec![],
// Compound statements
=> {
statements.push(next);
statements
},
// Small statements
";")*> ";"? "\n" => {
statements.extend(small);
statements.push(last);
statements
},
// Empty lines
"\n" => s,
};
Suite: ast::Suite = {
";")*> ";"? "\n" => {
statements.push(last);
statements
},
"\n" Indent Dedent => s,
};
// One or more statements
Statements: Vec = {
// First simple statement
";")*> ";"? "\n" => {
head.push(last);
head
},
// The first compound statement
=> vec![s],
// Any subsequent compound statements
=> {
statements.push(next);
statements
},
// Any subsequent small statements
";")*> ";"? "\n" => {
statements.extend(small);
statements.push(last);
statements
},
};
SmallStatement: ast::Stmt = {
ExpressionStatement,
PassStatement,
DelStatement,
FlowStatement,
ImportStatement,
GlobalStatement,
NonlocalStatement,
AssertStatement,
TypeAliasStatement,
IpyEscapeCommandStatement,
IpyHelpEndEscapeCommandStatement,
};
PassStatement: ast::Stmt = {
"pass" => {
ast::Stmt::Pass(ast::StmtPass { range: (location..end_location).into() })
},
};
DelStatement: ast::Stmt = {
"del" => {
ast::Stmt::Delete(
ast::StmtDelete { targets: targets.into_iter().map(|expr| set_context(expr, ast::ExprContext::Del)).collect(), range: (location..end_location).into() }
)
},
};
ExpressionStatement: ast::Stmt = {
=> {
// Just an expression, no assignment:
if suffix.is_empty() {
ast::Stmt::Expr(
ast::StmtExpr { value: Box::new(expression), range: (location..end_location).into() }
)
} else {
let mut targets = vec![set_context(expression, ast::ExprContext::Store)];
let mut values = suffix;
let value = Box::new(values.pop().unwrap());
for target in values {
targets.push(set_context(target, ast::ExprContext::Store));
}
ast::Stmt::Assign(
ast::StmtAssign { targets, value, range: (location..end_location).into() }
)
}
},
=> {
ast::Stmt::AugAssign(
ast::StmtAugAssign {
target: Box::new(set_context(target, ast::ExprContext::Store)),
op,
value: Box::new(rhs),
range: (location..end_location).into()
},
)
},
> ":" > => {
let simple = target.is_name_expr();
ast::Stmt::AnnAssign(
ast::StmtAnnAssign {
target: Box::new(set_context(target, ast::ExprContext::Store)),
annotation: Box::new(annotation),
value: rhs.map(Box::new),
simple,
range: (location..end_location).into()
},
)
},
};
AssignSuffix: ast::Expr = {
"=" => e,
"=" => e
};
TestListOrYieldExpr: ast::Expr = {
TestList,
YieldExpr
}
#[inline]
TestOrStarExprList: ast::Expr = {
// as far as I can tell, these were the same
TestList
};
TestOrStarExpr: ast::Expr = {
Test<"all">,
StarExpr,
};
NamedOrStarExpr: ast::Expr = {
NamedExpression,
StarExpr,
};
TestOrStarNamedExpr: ast::Expr = {
NamedExpressionTest,
StarExpr,
};
AugAssign: ast::Operator = {
"+=" => ast::Operator::Add,
"-=" => ast::Operator::Sub,
"*=" => ast::Operator::Mult,
"@=" => ast::Operator::MatMult,
"/=" => ast::Operator::Div,
"%=" => ast::Operator::Mod,
"&=" => ast::Operator::BitAnd,
"|=" => ast::Operator::BitOr,
"^=" => ast::Operator::BitXor,
"<<=" => ast::Operator::LShift,
">>=" => ast::Operator::RShift,
"**=" => ast::Operator::Pow,
"//=" => ast::Operator::FloorDiv,
};
FlowStatement: ast::Stmt = {
"break" => {
ast::Stmt::Break(ast::StmtBreak { range: (location..end_location).into() })
},
"continue" => {
ast::Stmt::Continue(ast::StmtContinue { range: (location..end_location).into() })
},
"return" => {
ast::Stmt::Return(
ast::StmtReturn { value: value.map(Box::new), range: (location..end_location).into() }
)
},
=> {
ast::Stmt::Expr(
ast::StmtExpr { value: Box::new(expression), range: (location..end_location).into() }
)
},
RaiseStatement,
};
RaiseStatement: ast::Stmt = {
"raise" => {
ast::Stmt::Raise(
ast::StmtRaise { exc: None, cause: None, range: (location..end_location).into() }
)
},
"raise" > >)?> => {
ast::Stmt::Raise(
ast::StmtRaise { exc: Some(Box::new(t)), cause: c.map(Box::new), range: (location..end_location).into() }
)
},
};
ImportStatement: ast::Stmt = {
"import" >> => {
ast::Stmt::Import(
ast::StmtImport { names, range: (location..end_location).into() }
)
},
"from" "import" => {
let (level, module) = source;
ast::Stmt::ImportFrom(
ast::StmtImportFrom {
level,
module,
names,
range: (location..end_location).into()
},
)
},
};
ImportFromLocation: (Option, Option) = {
=> {
(Some(ast::Int::new(dots.iter().map(ast::Int::to_u32).sum())), Some(name))
},
=> {
(Some(ast::Int::new(dots.iter().map(ast::Int::to_u32).sum())), None)
},
};
ImportDots: ast::Int = {
"..." => ast::Int::new(3),
"." => ast::Int::new(1),
};
ImportAsNames: Vec = {
>> => i,
"(" >> ","? ")" => i,
"*" => {
// Star import all
vec![ast::Alias { name: ast::Identifier::new("*", (location..end_location).into()), asname: None, range: (location..end_location).into() }]
},
};
#[inline]
ImportAsAlias: ast::Alias = {
)?> => ast::Alias { name, asname: a, range: (location..end_location).into() },
}
// A name like abc or abc.def.ghi
DottedName: ast::Identifier = {
=> ast::Identifier::new(n, (location..end_location).into()),
=> {
let mut r = n;
for x in n2 {
r.push('.');
r.push_str(x.1.as_str());
}
ast::Identifier::new(r, (location..end_location).into())
},
};
GlobalStatement: ast::Stmt = {
"global" > => {
ast::Stmt::Global(
ast::StmtGlobal { names, range: (location..end_location).into() }
)
},
};
NonlocalStatement: ast::Stmt = {
"nonlocal" > => {
ast::Stmt::Nonlocal(
ast::StmtNonlocal { names, range: (location..end_location).into() }
)
},
};
AssertStatement: ast::Stmt = {
"assert" > >)?> => {
ast::Stmt::Assert(
ast::StmtAssert {
test: Box::new(test),
msg: msg.map(Box::new),
range: (location..end_location).into()
}
)
},
};
IpyEscapeCommandStatement: ast::Stmt = {
=>? {
if mode == Mode::Jupyter {
Ok(ast::Stmt::IpyEscapeCommand(
ast::StmtIpyEscapeCommand {
kind: c.0,
value: c.1,
range: (location..end_location).into()
}
))
} else {
Err(LexicalError {
error: LexicalErrorType::OtherError("IPython escape commands are only allowed in Jupyter mode".to_string()),
location,
})?
}
}
}
IpyEscapeCommandExpr: ast::Expr = {
=>? {
if mode == Mode::Jupyter {
// This should never occur as the lexer won't allow it.
if !matches!(c.0, IpyEscapeKind::Magic | IpyEscapeKind::Shell) {
return Err(LexicalError {
error: LexicalErrorType::OtherError("IPython escape command expr is only allowed for % and !".to_string()),
location,
})?;
}
Ok(ast::Expr::IpyEscapeCommand(
ast::ExprIpyEscapeCommand {
kind: c.0,
value: c.1,
range: (location..end_location).into()
}
))
} else {
Err(LexicalError {
error: LexicalErrorType::OtherError("IPython escape commands are only allowed in Jupyter mode".to_string()),
location,
})?
}
}
}
IpyHelpEndEscapeCommandStatement: ast::Stmt = {
// We are permissive than the original implementation because we would allow whitespace
// between the expression and the suffix while the IPython implementation doesn't allow it.
// For example, `foo ?` would be valid in our case but invalid from IPython.
> =>? {
fn unparse_expr(expr: &ast::Expr, buffer: &mut String) -> Result<(), LexicalError> {
match expr {
ast::Expr::Name(ast::ExprName { id, .. }) => {
buffer.push_str(id.as_str());
},
ast::Expr::Subscript(ast::ExprSubscript { value, slice, range, .. }) => {
let ast::Expr::Constant(ast::ExprConstant { value: ast::Constant::Int(integer), .. }) = slice.as_ref() else {
return Err(LexicalError {
error: LexicalErrorType::OtherError("only integer constants are allowed in Subscript expressions in help end escape command".to_string()),
location: range.start(),
});
};
unparse_expr(value, buffer)?;
buffer.push('[');
buffer.push_str(&format!("{}", integer));
buffer.push(']');
},
ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) => {
unparse_expr(value, buffer)?;
buffer.push('.');
buffer.push_str(attr.as_str());
},
_ => {
return Err(LexicalError {
error: LexicalErrorType::OtherError("only Name, Subscript and Attribute expressions are allowed in help end escape command".to_string()),
location: expr.range().start(),
});
}
}
Ok(())
}
if mode != Mode::Jupyter {
return Err(ParseError::User {
error: LexicalError {
error: LexicalErrorType::OtherError("IPython escape commands are only allowed in Jupyter mode".to_string()),
location,
},
});
}
let kind = match suffix.len() {
1 => IpyEscapeKind::Help,
2 => IpyEscapeKind::Help2,
_ => {
return Err(ParseError::User {
error: LexicalError {
error: LexicalErrorType::OtherError("maximum of 2 `?` tokens are allowed in help end escape command".to_string()),
location,
},
});
}
};
let mut value = String::new();
unparse_expr(&e, &mut value)?;
Ok(ast::Stmt::IpyEscapeCommand(
ast::StmtIpyEscapeCommand {
kind,
value,
range: (location..end_location).into()
}
))
}
}
CompoundStatement: ast::Stmt = {
MatchStatement,
IfStatement,
WhileStatement,
ForStatement,
TryStatement,
WithStatement,
FuncDef,
ClassDef,
};
MatchStatement: ast::Stmt = {
"match" ":" "\n" Indent Dedent => {
let end_location = cases
.last()
.unwrap()
.body
.last()
.unwrap()
.end();
ast::Stmt::Match(
ast::StmtMatch {
subject: Box::new(subject),
cases,
range: (location..end_location).into()
}
)
},
"match" "," ":" "\n" Indent Dedent => {
let end_location = cases
.last()
.unwrap()
.body
.last()
.unwrap()
.end();
ast::Stmt::Match(
ast::StmtMatch {
subject: Box::new(subject),
cases,
range: (location..end_location).into()
}
)
},
"match" > ","? ":" "\n" Indent Dedent => {
let end_location = cases
.last()
.unwrap()
.body
.last()
.unwrap()
.end();
ast::Stmt::Match(
ast::StmtMatch {
subject: Box::new(ast::Expr::Tuple(
ast::ExprTuple {
elts: subjects,
ctx: ast::ExprContext::Load,
range: (location..end_location).into()
},
)),
cases,
range: (location..end_location).into()
}
)
}
}
MatchCase: ast::MatchCase = {
"case" ":" => {
// SAFETY: `body` is never empty because it is non-optional and `Suite` matches one or more statements.
let end = body.last().unwrap().end();
ast::MatchCase {
pattern,
guard: guard.map(Box::new),
body,
range: (start..end).into()
}
},
}
Guard: ast::Expr = {
"if" => {
guard
}
}
Patterns: ast::Pattern = {
"," => ast::Pattern::MatchSequence(
ast::PatternMatchSequence {
patterns: vec![pattern],
range: (location..end_location).into()
},
),
> ","? => {
ast::Pattern::MatchSequence(
ast::PatternMatchSequence {
patterns,
range: (location..end_location).into()
},
)
},
=> pattern
}
Pattern: ast::Pattern = {
=> pattern,
=> pattern,
}
AsPattern: ast::Pattern = {
"as" =>? {
if name.as_str() == "_" {
Err(LexicalError {
error: LexicalErrorType::OtherError("cannot use '_' as a target".to_string()),
location,
})?
} else {
Ok(ast::Pattern::MatchAs(
ast::PatternMatchAs {
pattern: Some(Box::new(pattern)),
name: Some(name),
range: (location..end_location).into()
},
))
}
},
}
OrPattern: ast::Pattern = {
=> pattern,
> => {
ast::Pattern::MatchOr(
ast::PatternMatchOr { patterns, range: (location..end_location).into() }
)
}
}
ClosedPattern: ast::Pattern = {
=> node,
=> node,
=> node,
=> node,
=> node,
=> node,
=> node,
}
SequencePattern: ast::Pattern = {
// A single-item tuple is a special case: it's a group pattern, _not_ a sequence pattern.
"(" ")" => pattern,
"(" ")" => ast::PatternMatchSequence {
patterns: vec![],
range: (location..end_location).into()
}.into(),
"(" "," ")" => {
ast::PatternMatchSequence {
patterns: vec![pattern],
range: (location..end_location).into()
}.into()
},
"(" ",")+> ","? ")" => {
let mut patterns = patterns;
patterns.push(last);
ast::PatternMatchSequence {
patterns,
range: (location..end_location).into()
}.into()
},
"[" > "]" => ast::PatternMatchSequence {
patterns,
range: (location..end_location).into()
}.into(),
}
StarPattern: ast::Pattern = {
"*" => ast::PatternMatchStar {
name: if name.as_str() == "_" { None } else { Some(name) },
range: (location..end_location).into()
}.into(),
}
ConstantAtom: ast::Expr = {
=> ast::Expr::Constant(
ast::ExprConstant { value, kind: None, range: (location..end_location).into() }
),
}
ConstantExpr: ast::Expr = {
ConstantAtom,
"-" => ast::Expr::UnaryOp(
ast::ExprUnaryOp {
op: ast::UnaryOp::USub,
operand: Box::new(operand),
range: (location..end_location).into()
}
),
}
AddOpExpr: ast::Expr = {
=> ast::Expr::BinOp(
ast::ExprBinOp {
left: Box::new(left),
op,
right: Box::new(right),
range: (location..end_location).into()
}
),
}
LiteralPattern: ast::Pattern = {
"None" => ast::PatternMatchSingleton {
value: ast::Constant::None,
range: (location..end_location).into()
}.into(),
"True" => ast::PatternMatchSingleton {
value: true.into(),
range: (location..end_location).into()
}.into(),
"False" => ast::PatternMatchSingleton {
value: false.into(),
range: (location..end_location).into()
}.into(),
=> ast::PatternMatchValue {
value: Box::new(value),
range: (location..end_location).into()
}.into(),
=> ast::PatternMatchValue {
value: Box::new(value),
range: (location..end_location).into()
}.into(),
=>? Ok(ast::PatternMatchValue {
value: Box::new(parse_strings(s)?),
range: (location..end_location).into()
}.into()),
}
CapturePattern: ast::Pattern = {
=> ast::PatternMatchAs {
pattern: None,
name: if name.as_str() == "_" { None } else { Some(name) },
range: (location..end_location).into()
}.into(),
}
MatchName: ast::Expr = {
=> ast::Expr::Name(
ast::ExprName { id: id.into(), ctx: ast::ExprContext::Load, range: (location..end_location).into() },
),
}
MatchNameOrAttr: ast::Expr = {
"." => ast::Expr::Attribute(
ast::ExprAttribute {
value: Box::new(name),
attr,
ctx: ast::ExprContext::Load,
range: (location..end_location).into()
},
),
"." => ast::Expr::Attribute(
ast::ExprAttribute {
value: Box::new(e),
attr,
ctx: ast::ExprContext::Load,
range: (location..end_location).into()
},
)
}
ValuePattern: ast::Pattern = {
=> ast::PatternMatchValue {
value: Box::new(e),
range: (location..end_location).into()
}.into(),
}
MappingKey: ast::Expr = {
ConstantExpr,
AddOpExpr,
MatchNameOrAttr,
"None" => ast::Expr::Constant(
ast::ExprConstant {
value: ast::Constant::None,
kind: None,
range: (location..end_location).into()
},
),
"True" => ast::Expr::Constant(
ast::ExprConstant {
value: true.into(),
kind: None,
range: (location..end_location).into()
},
),
"False" => ast::Expr::Constant(
ast::ExprConstant {
value: false.into(),
kind: None,
range: (location..end_location).into()
},
),
=>? Ok(parse_strings(s)?),
}
MatchMappingEntry: (ast::Expr, ast::Pattern) = {
":" => (k, v),
};
MappingPattern: ast::Pattern = {
"{" "}" => {
ast::PatternMatchMapping {
keys: vec![],
patterns: vec![],
rest: None,
range: (location..end_location).into()
}.into()
},
"{" > ","? "}" => {
let (keys, patterns) = e
.into_iter()
.unzip();
ast::PatternMatchMapping {
keys,
patterns,
rest: None,
range: (location..end_location).into()
}.into()
},
"{" "**" ","? "}" => {
ast::PatternMatchMapping {
keys: vec![],
patterns: vec![],
rest: Some(rest),
range: (location..end_location).into()
}.into()
},
"{" > "," "**" ","? "}" => {
let (keys, patterns) = e
.into_iter()
.unzip();
ast::PatternMatchMapping {
keys,
patterns,
rest: Some(rest),
range: (location..end_location).into()
}.into()
},
}
MatchKeywordEntry: (ast::Identifier, ast::Pattern) = {
"=" => (k, v),
};
ClassPattern: ast::Pattern = {
"(" > "," > ","? ")" => {
let (kwd_attrs, kwd_patterns) = kwds
.into_iter()
.unzip();
ast::PatternMatchClass {
cls: Box::new(e),
patterns,
kwd_attrs,
kwd_patterns,
range: (location..end_location).into()
}.into()
},
"(" > ","? ")" => {
ast::PatternMatchClass {
cls: Box::new(e),
patterns,
kwd_attrs: vec![],
kwd_patterns: vec![],
range: (location..end_location).into()
}.into()
},
"(" > ","? ")" => {
let (kwd_attrs, kwd_patterns) = kwds
.into_iter()
.unzip();
ast::PatternMatchClass {
cls: Box::new(e),
patterns: vec![],
kwd_attrs,
kwd_patterns,
range: (location..end_location).into()
}.into()
},
"(" ")" => {
ast::PatternMatchClass {
cls: Box::new(e),
patterns: vec![],
kwd_attrs: vec![],
kwd_patterns: vec![],
range: (location..end_location).into()
}.into()
},