mirror of https://github.com/astral-sh/ruff
Lex Jupyter Magic in assignment value position (#30)
Emit `MagicCommand` token when it is the assignment value[^1] i.e., on
the right side of an assignment statement.
Examples:
```python
pwd = !pwd
foo = %timeit a = b
bar = %timeit a % 3
baz = %matplotlib \
inline"
```
[^1]: Only `%` and `!` are valid in that position, other magic kinds are
not valid
This commit is contained in:
parent
4888d800fb
commit
e363fb860e
|
|
@ -175,6 +175,8 @@ pub struct Lexer<T: Iterator<Item = char>> {
|
||||||
pending: Vec<Spanned>,
|
pending: Vec<Spanned>,
|
||||||
// The current location.
|
// The current location.
|
||||||
location: TextSize,
|
location: TextSize,
|
||||||
|
// Is the last token an equal sign?
|
||||||
|
last_token_is_equal: bool,
|
||||||
// Lexer mode.
|
// Lexer mode.
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
}
|
}
|
||||||
|
|
@ -233,6 +235,7 @@ where
|
||||||
pending: Vec::with_capacity(5),
|
pending: Vec::with_capacity(5),
|
||||||
location: start,
|
location: start,
|
||||||
window: CharWindow::new(input),
|
window: CharWindow::new(input),
|
||||||
|
last_token_is_equal: false,
|
||||||
mode,
|
mode,
|
||||||
};
|
};
|
||||||
// Fill the window.
|
// Fill the window.
|
||||||
|
|
@ -945,15 +948,19 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'%' => {
|
'%' => {
|
||||||
let tok_start = self.get_pos();
|
if self.mode == Mode::Jupyter && self.nesting == 0 && self.last_token_is_equal {
|
||||||
self.next_char();
|
self.lex_and_emit_magic_command();
|
||||||
if let Some('=') = self.window[0] {
|
|
||||||
self.next_char();
|
|
||||||
let tok_end = self.get_pos();
|
|
||||||
self.emit((Tok::PercentEqual, TextRange::new(tok_start, tok_end)));
|
|
||||||
} else {
|
} else {
|
||||||
let tok_end = self.get_pos();
|
let tok_start = self.get_pos();
|
||||||
self.emit((Tok::Percent, TextRange::new(tok_start, tok_end)));
|
self.next_char();
|
||||||
|
if let Some('=') = self.window[0] {
|
||||||
|
self.next_char();
|
||||||
|
let tok_end = self.get_pos();
|
||||||
|
self.emit((Tok::PercentEqual, TextRange::new(tok_start, tok_end)));
|
||||||
|
} else {
|
||||||
|
let tok_end = self.get_pos();
|
||||||
|
self.emit((Tok::Percent, TextRange::new(tok_start, tok_end)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'|' => {
|
'|' => {
|
||||||
|
|
@ -1025,17 +1032,21 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'!' => {
|
'!' => {
|
||||||
let tok_start = self.get_pos();
|
if self.mode == Mode::Jupyter && self.nesting == 0 && self.last_token_is_equal {
|
||||||
self.next_char();
|
self.lex_and_emit_magic_command();
|
||||||
if let Some('=') = self.window[0] {
|
|
||||||
self.next_char();
|
|
||||||
let tok_end = self.get_pos();
|
|
||||||
self.emit((Tok::NotEqual, TextRange::new(tok_start, tok_end)));
|
|
||||||
} else {
|
} else {
|
||||||
return Err(LexicalError {
|
let tok_start = self.get_pos();
|
||||||
error: LexicalErrorType::UnrecognizedToken { tok: '!' },
|
self.next_char();
|
||||||
location: tok_start,
|
if let Some('=') = self.window[0] {
|
||||||
});
|
self.next_char();
|
||||||
|
let tok_end = self.get_pos();
|
||||||
|
self.emit((Tok::NotEqual, TextRange::new(tok_start, tok_end)));
|
||||||
|
} else {
|
||||||
|
return Err(LexicalError {
|
||||||
|
error: LexicalErrorType::UnrecognizedToken { tok: '!' },
|
||||||
|
location: tok_start,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'~' => {
|
'~' => {
|
||||||
|
|
@ -1292,6 +1303,7 @@ where
|
||||||
|
|
||||||
// Helper function to emit a lexed token to the queue of tokens.
|
// Helper function to emit a lexed token to the queue of tokens.
|
||||||
fn emit(&mut self, spanned: Spanned) {
|
fn emit(&mut self, spanned: Spanned) {
|
||||||
|
self.last_token_is_equal = matches!(spanned.0, Tok::Equal);
|
||||||
self.pending.push(spanned);
|
self.pending.push(spanned);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1669,6 +1681,85 @@ mod tests {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_jupyter_magic_assignment() {
|
||||||
|
let source = r"
|
||||||
|
pwd = !pwd
|
||||||
|
foo = %timeit a = b
|
||||||
|
bar = %timeit a % 3
|
||||||
|
baz = %matplotlib \
|
||||||
|
inline"
|
||||||
|
.trim();
|
||||||
|
let tokens = lex_jupyter_source(source);
|
||||||
|
assert_eq!(
|
||||||
|
tokens,
|
||||||
|
vec![
|
||||||
|
Tok::Name {
|
||||||
|
name: "pwd".to_string()
|
||||||
|
},
|
||||||
|
Tok::Equal,
|
||||||
|
Tok::MagicCommand {
|
||||||
|
value: "pwd".to_string(),
|
||||||
|
kind: MagicKind::Shell,
|
||||||
|
},
|
||||||
|
Tok::Newline,
|
||||||
|
Tok::Name {
|
||||||
|
name: "foo".to_string()
|
||||||
|
},
|
||||||
|
Tok::Equal,
|
||||||
|
Tok::MagicCommand {
|
||||||
|
value: "timeit a = b".to_string(),
|
||||||
|
kind: MagicKind::Magic,
|
||||||
|
},
|
||||||
|
Tok::Newline,
|
||||||
|
Tok::Name {
|
||||||
|
name: "bar".to_string()
|
||||||
|
},
|
||||||
|
Tok::Equal,
|
||||||
|
Tok::MagicCommand {
|
||||||
|
value: "timeit a % 3".to_string(),
|
||||||
|
kind: MagicKind::Magic,
|
||||||
|
},
|
||||||
|
Tok::Newline,
|
||||||
|
Tok::Name {
|
||||||
|
name: "baz".to_string()
|
||||||
|
},
|
||||||
|
Tok::Equal,
|
||||||
|
Tok::MagicCommand {
|
||||||
|
value: "matplotlib inline".to_string(),
|
||||||
|
kind: MagicKind::Magic,
|
||||||
|
},
|
||||||
|
Tok::Newline,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_no_jupyter_magic(tokens: &[Tok]) {
|
||||||
|
for tok in tokens {
|
||||||
|
match tok {
|
||||||
|
Tok::MagicCommand { .. } => panic!("Unexpected magic command token: {:?}", tok),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_jupyter_magic_not_an_assignment() {
|
||||||
|
let source = r"
|
||||||
|
# Other magic kinds are not valid here (can't test `foo = ?str` because '?' is not a valid token)
|
||||||
|
foo = /func
|
||||||
|
foo = ;func
|
||||||
|
foo = ,func
|
||||||
|
|
||||||
|
(foo == %timeit a = b)
|
||||||
|
(foo := %timeit a = b)
|
||||||
|
def f(arg=%timeit a = b):
|
||||||
|
pass"
|
||||||
|
.trim();
|
||||||
|
let tokens = lex_jupyter_source(source);
|
||||||
|
assert_no_jupyter_magic(&tokens);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_numbers() {
|
fn test_numbers() {
|
||||||
let source = "0x2f 0o12 0b1101 0 123 123_45_67_890 0.2 1e+2 2.1e3 2j 2.2j";
|
let source = "0x2f 0o12 0b1101 0 123 123_45_67_890 0.2 1e+2 2.1e3 2j 2.2j";
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue