Skip to content
This repository was archived by the owner on Jul 27, 2023. It is now read-only.

Commit 5ef4ccd

Browse files
authored
Add line magic stmt and expr AST nodes (#31)
This PR introduces two new nodes for the parser to recognize the `MagicCommand` tokens: * `StmtLineMagic` for statement position i.e., magic commands on their own line: ```python %matplotlib inline !pwd ``` * `ExprLineMagic` for expression position i.e., magic commands in an assignment statement: ```python # Only `?` and `!` are valid in this position dir = !pwd ``` Both nodes are identical in their structure as in it contains the magic kind and the command value as `String`.
1 parent e363fb8 commit 5ef4ccd

File tree

10 files changed

+20554
-13412
lines changed

10 files changed

+20554
-13412
lines changed

ast/src/generic.rs

Lines changed: 140 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#![allow(clippy::derive_partial_eq_without_eq)]
22
use crate::text_size::TextRange;
33
pub use crate::{builtin::*, text_size::TextSize, ConversionFlag, Node};
4-
use std::fmt::Debug;
4+
use std::fmt::{self, Debug};
55

66
// This file was originally generated from asdl by a python script, but we now edit it manually
77

@@ -317,6 +317,32 @@ pub enum Stmt {
317317
Break(StmtBreak),
318318
#[is(name = "continue_stmt")]
319319
Continue(StmtContinue),
320+
321+
// Jupyter notebook specific
322+
#[is(name = "line_magic_stmt")]
323+
LineMagic(StmtLineMagic),
324+
}
325+
326+
#[derive(Clone, Debug, PartialEq)]
327+
pub struct StmtLineMagic {
328+
pub range: TextRange,
329+
pub kind: MagicKind,
330+
pub value: String,
331+
}
332+
333+
impl Node for StmtLineMagic {
334+
const NAME: &'static str = "LineMagic";
335+
const FIELD_NAMES: &'static [&'static str] = &["kind", "value"];
336+
}
337+
impl From<StmtLineMagic> for Stmt {
338+
fn from(payload: StmtLineMagic) -> Self {
339+
Stmt::LineMagic(payload)
340+
}
341+
}
342+
impl From<StmtLineMagic> for Ast {
343+
fn from(payload: StmtLineMagic) -> Self {
344+
Stmt::from(payload).into()
345+
}
320346
}
321347

322348
/// See also [FunctionDef](https://docs.python.org/3/library/ast.html#ast.FunctionDef)
@@ -1084,6 +1110,32 @@ pub enum Expr {
10841110
Tuple(ExprTuple),
10851111
#[is(name = "slice_expr")]
10861112
Slice(ExprSlice),
1113+
1114+
// Jupyter notebook specific
1115+
#[is(name = "line_magic_expr")]
1116+
LineMagic(ExprLineMagic),
1117+
}
1118+
1119+
#[derive(Clone, Debug, PartialEq)]
1120+
pub struct ExprLineMagic {
1121+
pub range: TextRange,
1122+
pub kind: MagicKind,
1123+
pub value: String,
1124+
}
1125+
1126+
impl Node for ExprLineMagic {
1127+
const NAME: &'static str = "LineMagic";
1128+
const FIELD_NAMES: &'static [&'static str] = &["kind", "value"];
1129+
}
1130+
impl From<ExprLineMagic> for Expr {
1131+
fn from(payload: ExprLineMagic) -> Self {
1132+
Expr::LineMagic(payload)
1133+
}
1134+
}
1135+
impl From<ExprLineMagic> for Ast {
1136+
fn from(payload: ExprLineMagic) -> Self {
1137+
Expr::from(payload).into()
1138+
}
10871139
}
10881140

10891141
/// See also [BoolOp](https://docs.python.org/3/library/ast.html#ast.BoolOp)
@@ -3358,3 +3410,90 @@ impl Arguments {
33583410
(args, with_defaults)
33593411
}
33603412
}
3413+
3414+
/// The kind of magic command as defined in [IPython Syntax] in the IPython codebase.
3415+
///
3416+
/// [IPython Syntax]: https://github.com/ipython/ipython/blob/635815e8f1ded5b764d66cacc80bbe25e9e2587f/IPython/core/inputtransformer2.py#L335-L343
3417+
#[derive(PartialEq, Eq, Debug, Clone, Hash, Copy)]
3418+
pub enum MagicKind {
3419+
/// Send line to underlying system shell.
3420+
Shell,
3421+
/// Send line to system shell and capture output.
3422+
ShCap,
3423+
/// Show help on object.
3424+
Help,
3425+
/// Show help on object, with extra verbosity.
3426+
Help2,
3427+
/// Call magic function.
3428+
Magic,
3429+
/// Call cell magic function.
3430+
Magic2,
3431+
/// Call first argument with rest of line as arguments after splitting on whitespace
3432+
/// and quote each as string.
3433+
Quote,
3434+
/// Call first argument with rest of line as an argument quoted as a single string.
3435+
Quote2,
3436+
/// Call first argument with rest of line as arguments.
3437+
Paren,
3438+
}
3439+
3440+
impl TryFrom<char> for MagicKind {
3441+
type Error = String;
3442+
3443+
fn try_from(ch: char) -> Result<Self, Self::Error> {
3444+
match ch {
3445+
'!' => Ok(MagicKind::Shell),
3446+
'?' => Ok(MagicKind::Help),
3447+
'%' => Ok(MagicKind::Magic),
3448+
',' => Ok(MagicKind::Quote),
3449+
';' => Ok(MagicKind::Quote2),
3450+
'/' => Ok(MagicKind::Paren),
3451+
_ => Err(format!("Unexpected magic escape: {ch}")),
3452+
}
3453+
}
3454+
}
3455+
3456+
impl TryFrom<[char; 2]> for MagicKind {
3457+
type Error = String;
3458+
3459+
fn try_from(ch: [char; 2]) -> Result<Self, Self::Error> {
3460+
match ch {
3461+
['!', '!'] => Ok(MagicKind::ShCap),
3462+
['?', '?'] => Ok(MagicKind::Help2),
3463+
['%', '%'] => Ok(MagicKind::Magic2),
3464+
[c1, c2] => Err(format!("Unexpected magic escape: {c1}{c2}")),
3465+
}
3466+
}
3467+
}
3468+
3469+
impl fmt::Display for MagicKind {
3470+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3471+
match self {
3472+
MagicKind::Shell => f.write_str("!"),
3473+
MagicKind::ShCap => f.write_str("!!"),
3474+
MagicKind::Help => f.write_str("?"),
3475+
MagicKind::Help2 => f.write_str("??"),
3476+
MagicKind::Magic => f.write_str("%"),
3477+
MagicKind::Magic2 => f.write_str("%%"),
3478+
MagicKind::Quote => f.write_str(","),
3479+
MagicKind::Quote2 => f.write_str(";"),
3480+
MagicKind::Paren => f.write_str("/"),
3481+
}
3482+
}
3483+
}
3484+
3485+
impl MagicKind {
3486+
/// Returns the length of the magic command prefix.
3487+
pub fn prefix_len(self) -> TextSize {
3488+
let len = match self {
3489+
MagicKind::Shell
3490+
| MagicKind::Magic
3491+
| MagicKind::Help
3492+
| MagicKind::Quote
3493+
| MagicKind::Quote2
3494+
| MagicKind::Paren => 1,
3495+
MagicKind::ShCap | MagicKind::Magic2 | MagicKind::Help2 => 2,
3496+
};
3497+
len.into()
3498+
}
3499+
}

ast/src/impls.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ impl Expr {
4949
Expr::Lambda { .. } => "lambda",
5050
Expr::IfExp { .. } => "conditional expression",
5151
Expr::NamedExpr { .. } => "named expression",
52+
Expr::LineMagic(_) => "line magic",
5253
}
5354
}
5455
}

ast/src/ranged.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,11 @@ impl Ranged for crate::generic::StmtContinue {
207207
self.range
208208
}
209209
}
210+
impl Ranged for crate::generic::StmtLineMagic {
211+
fn range(&self) -> TextRange {
212+
self.range
213+
}
214+
}
210215
impl Ranged for crate::Stmt {
211216
fn range(&self) -> TextRange {
212217
match self {
@@ -238,6 +243,7 @@ impl Ranged for crate::Stmt {
238243
Self::Pass(node) => node.range(),
239244
Self::Break(node) => node.range(),
240245
Self::Continue(node) => node.range(),
246+
Self::LineMagic(node) => node.range(),
241247
}
242248
}
243249
}
@@ -377,6 +383,11 @@ impl Ranged for crate::generic::ExprSlice {
377383
self.range
378384
}
379385
}
386+
impl Ranged for crate::generic::ExprLineMagic {
387+
fn range(&self) -> TextRange {
388+
self.range
389+
}
390+
}
380391
impl Ranged for crate::Expr {
381392
fn range(&self) -> TextRange {
382393
match self {
@@ -407,6 +418,7 @@ impl Ranged for crate::Expr {
407418
Self::List(node) => node.range(),
408419
Self::Tuple(node) => node.range(),
409420
Self::Slice(node) => node.range(),
421+
Self::LineMagic(node) => node.range(),
410422
}
411423
}
412424
}

core/src/mode.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,12 @@ pub enum Mode {
1515
///
1616
/// ## Limitations:
1717
///
18-
/// These escaped commands are only supported when they are the only
19-
/// statement on a line. If they're part of a larger statement such as
20-
/// on the right-hand side of an assignment, the lexer will not recognize
21-
/// them as escape commands.
22-
///
2318
/// For [Dynamic object information], the escape characters (`?`, `??`)
2419
/// must be used before an object. For example, `?foo` will be recognized,
2520
/// but `foo?` will not.
2621
///
2722
/// ## Supported escape commands:
23+
///
2824
/// - [Magic command system] which is limited to [line magics] and can start
2925
/// with `?` or `??`.
3026
/// - [Dynamic object information] which can start with `?` or `??`.

0 commit comments

Comments
 (0)