Remove unused rust impl for formatting dis output (#7660)

* Remove unused rust impl for formatting dis output

* remove `examples/dis.rs`

* Added tests

* Update lock

* Try to set snapshot dir

* Remove verbose flag

* Regenerate snapshots after #7711

* Revert "Bump insta from 1.46.3 to 1.47.2 (#7706)"

This reverts commit e6d9ea6bfe.

* Debug info

* Show diff as well

* Show debug faster

* CI: true env

* Recert CI

* Add `CI: true` in ci emv

* Reapply "Bump insta from 1.46.3 to 1.47.2 (#7706)"

This reverts commit 693ca8cbe4d7885a81162a9be31e8bb567db885a.

* simplify macro

* trim on function side

* Force insta workspace root

* fix merge
This commit is contained in:
Shahar Naveh
2026-05-05 11:09:35 +03:00
committed by GitHub
parent ae7ff9c481
commit 02a2b19839
30 changed files with 474 additions and 1023 deletions

View File

@@ -34,6 +34,7 @@ env:
CARGO_PROFILE_RELEASE_DEBUG: 0
CARGO_TERM_COLOR: always
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true # TODO: Remove on 2026/06/02
CI: true
jobs:
determine_changes:
@@ -111,7 +112,9 @@ jobs:
uses: ./.github/actions/install-macos-deps
- name: run rust tests
run: cargo test --workspace ${{ env.WORKSPACE_EXCLUDES }} --verbose --features threading ${{ env.CARGO_ARGS }}
run: cargo test --workspace ${{ env.WORKSPACE_EXCLUDES }} --features threading ${{ env.CARGO_ARGS }}
env:
INSTA_WORKSPACE_ROOT: ${{ github.workspace }}
- run: cargo doc --locked
if: runner.os == 'Linux'

3
Cargo.lock generated
View File

@@ -3158,7 +3158,6 @@ dependencies = [
"ahash",
"bitflags 2.11.0",
"indexmap",
"insta",
"itertools 0.14.0",
"log",
"malachite-bigint",
@@ -3427,6 +3426,7 @@ dependencies = [
"icu_normalizer",
"icu_properties",
"indexmap",
"insta",
"itertools 0.14.0",
"libc",
"liblzma",
@@ -3464,6 +3464,7 @@ dependencies = [
"rustpython-common",
"rustpython-derive",
"rustpython-host_env",
"rustpython-pylib",
"rustpython-ruff_python_ast",
"rustpython-ruff_python_parser",
"rustpython-ruff_source_file",

View File

@@ -33,7 +33,6 @@ unicode_names2 = { workspace = true }
[dev-dependencies]
ruff_python_parser = { workspace = true }
insta = { workspace = true }
[lints]
workspace = true

View File

@@ -12275,26 +12275,6 @@ def f(sys, os, file):
})
}
macro_rules! assert_dis_snapshot {
($value:expr) => {
insta::assert_snapshot!(
insta::internals::AutoName,
$value.display_expand_code_objects().to_string(),
stringify!($value)
)
};
}
#[test]
fn test_if_ors() {
assert_dis_snapshot!(compile_exec(
"\
if True or False or False:
pass
"
));
}
#[test]
fn test_trace_assert_true_try_pair() {
let trace = compile_exec_late_cfg_trace(
@@ -12529,44 +12509,6 @@ def f(self):
);
}
#[test]
fn test_if_ands() {
assert_dis_snapshot!(compile_exec(
"\
if True and False and False:
pass
"
));
}
#[test]
fn test_if_mixed() {
assert_dis_snapshot!(compile_exec(
"\
if (True and False) or (False and True):
pass
"
));
}
#[test]
fn test_nested_bool_op() {
assert_dis_snapshot!(compile_exec(
"\
x = Test() and False or False
"
));
}
#[test]
fn test_const_bool_not_op() {
assert_dis_snapshot!(compile_exec_optimized(
"\
x = not True
"
));
}
#[test]
fn test_plain_constant_bool_op_folds_to_selected_operand() {
let code = compile_exec(
@@ -12840,24 +12782,6 @@ def f(self, mod):
);
}
#[test]
fn test_nested_double_async_with() {
assert_dis_snapshot!(compile_exec(
"\
async def test():
for stop_exc in (StopIteration('spam'), StopAsyncIteration('ham')):
with self.subTest(type=type(stop_exc)):
try:
async with egg():
raise stop_exc
except Exception as ex:
self.assertIs(ex, stop_exc)
else:
self.fail(f'{stop_exc} was suppressed')
"
));
}
#[test]
fn test_scope_exit_instructions_keep_line_numbers() {
let code = compile_exec(
@@ -14006,20 +13930,6 @@ def f(expected_ns, namespace):
}
}
#[test]
fn test_bare_function_annotations_check_attribute_and_subscript_expressions() {
assert_dis_snapshot!(compile_exec(
"\
def f(one: int):
int.new_attr: int
[list][0].new_attr: [int, str]
my_lst = [1]
my_lst[one]: int
return my_lst
"
));
}
#[test]
fn test_non_simple_bare_name_annotation_does_not_create_local_binding() {
let code = compile_exec(
@@ -14052,16 +13962,6 @@ def f2bad():
);
}
#[test]
fn test_constant_true_if_pass_keeps_line_anchor_nop() {
assert_dis_snapshot!(compile_exec(
"\
if 1:
pass
"
));
}
#[test]
fn test_negative_constant_binop_folds_after_unary_folding() {
let code = compile_exec(

View File

@@ -9378,8 +9378,7 @@ impl CodeInfo {
} else {
OpArg::new(ins.target.0)
};
let instr_display = instr.display(display_arg, self);
eprint!("{instr_display}: {depth} {effect:+} => ");
eprint!("{display_arg:?}: {depth} {effect:+} => ");
}
let new_depth = depth.checked_add_signed(effect).ok_or({
if effect < 0 {

View File

@@ -1,67 +0,0 @@
---
source: crates/codegen/src/compile.rs
assertion_line: 12138
expression: "compile_exec(\"\\\ndef f(one: int):\n int.new_attr: int\n [list][0].new_attr: [int, str]\n my_lst = [1]\n my_lst[one]: int\n return my_lst\n\")"
---
1 0 RESUME (0)
1 LOAD_CONST (<code object __annotate__ at ??? file "source_path", line 1>): 1 0 RESUME (0)
1 LOAD_FAST_BORROW (0, format)
2 LOAD_SMALL_INT (2)
>> 3 COMPARE_OP (>)
4 CACHE
5 POP_JUMP_IF_FALSE (3)
6 CACHE
7 NOT_TAKEN
8 LOAD_COMMON_CONSTANT (NotImplementedError)
9 RAISE_VARARGS (Raise)
10 LOAD_CONST ("one")
11 LOAD_GLOBAL (0, int)
12 CACHE
13 CACHE
14 CACHE
15 CACHE
16 BUILD_MAP (1)
17 RETURN_VALUE
2 MAKE_FUNCTION
3 LOAD_CONST (<code object f at ??? file "source_path", line 1>): 1 0 RESUME (0)
2 1 LOAD_GLOBAL (0, int)
2 CACHE
3 CACHE
4 CACHE
5 CACHE
6 POP_TOP
3 7 LOAD_GLOBAL (2, list)
8 CACHE
9 CACHE
10 CACHE
11 CACHE
12 BUILD_LIST (1)
13 LOAD_SMALL_INT (0)
14 BINARY_OP ([])
15 CACHE
16 CACHE
17 CACHE
18 CACHE
19 CACHE
20 POP_TOP
4 21 LOAD_SMALL_INT (1)
22 BUILD_LIST (1)
23 STORE_FAST (1, my_lst)
5 24 LOAD_FAST_BORROW (1, my_lst)
25 POP_TOP
26 LOAD_FAST_BORROW (0, one)
27 POP_TOP
6 28 LOAD_FAST_BORROW (1, my_lst)
29 RETURN_VALUE
4 MAKE_FUNCTION
5 SET_FUNCTION_ATTRIBUTE(Annotate)
6 STORE_NAME (0, f)
7 LOAD_CONST (None)
8 RETURN_VALUE

View File

@@ -1,9 +0,0 @@
---
source: crates/codegen/src/compile.rs
expression: "compile_exec_optimized(\"\\\nx = not True\n\")"
---
1 0 RESUME (0)
1 LOAD_CONST (False)
2 STORE_NAME (0, x)
3 LOAD_CONST (None)
4 RETURN_VALUE

View File

@@ -1,10 +0,0 @@
---
source: crates/codegen/src/compile.rs
assertion_line: 12222
expression: "compile_exec(\"\\\nif 1:\n pass\n\")"
---
1 0 RESUME (0)
1 NOP
2 2 LOAD_CONST (None)
3 RETURN_VALUE

View File

@@ -1,8 +0,0 @@
---
source: crates/codegen/src/compile.rs
assertion_line: 11413
expression: "compile_exec(\"\\\nif True and False and False:\n pass\n\")"
---
1 0 RESUME (0)
1 LOAD_CONST (None)
2 RETURN_VALUE

View File

@@ -1,8 +0,0 @@
---
source: crates/codegen/src/compile.rs
assertion_line: 11423
expression: "compile_exec(\"\\\nif (True and False) or (False and True):\n pass\n\")"
---
1 0 RESUME (0)
1 LOAD_CONST (None)
2 RETURN_VALUE

View File

@@ -1,10 +0,0 @@
---
source: crates/codegen/src/compile.rs
assertion_line: 11039
expression: "compile_exec(\"\\\nif True or False or False:\n pass\n\")"
---
1 0 RESUME (0)
1 NOP
2 2 LOAD_CONST (None)
3 RETURN_VALUE

View File

@@ -1,35 +0,0 @@
---
source: crates/codegen/src/compile.rs
assertion_line: 11769
expression: "compile_exec(\"\\\nx = Test() and False or False\n\")"
---
1 0 RESUME (0)
1 LOAD_NAME (0, Test)
2 PUSH_NULL
>> 3 CALL (0)
4 CACHE
5 CACHE
6 CACHE
7 COPY (1)
8 TO_BOOL
9 CACHE
10 CACHE
>> 11 CACHE
12 POP_JUMP_IF_FALSE (11)
13 CACHE
14 NOT_TAKEN
15 POP_TOP
16 LOAD_CONST (False)
17 COPY (1)
18 TO_BOOL
19 CACHE
20 CACHE
21 CACHE
22 POP_JUMP_IF_TRUE (3)
23 CACHE
24 NOT_TAKEN
25 POP_TOP
26 LOAD_CONST (False)
27 STORE_NAME (1, x)
28 LOAD_CONST (None)
29 RETURN_VALUE

View File

@@ -1,258 +0,0 @@
---
source: crates/codegen/src/compile.rs
assertion_line: 11847
expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIteration('spam'), StopAsyncIteration('ham')):\n with self.subTest(type=type(stop_exc)):\n try:\n async with egg():\n raise stop_exc\n except Exception as ex:\n self.assertIs(ex, stop_exc)\n else:\n self.fail(f'{stop_exc} was suppressed')\n\")"
---
1 0 RESUME (0)
1 LOAD_CONST (<code object test at ??? file "source_path", line 1>): 1 0 RETURN_GENERATOR
1 POP_TOP
>> 2 RESUME (0)
2 >> 3 LOAD_GLOBAL (1, NULL + StopIteration)
>> 4 CACHE
>> 5 CACHE
6 CACHE
7 CACHE
>> 8 LOAD_CONST ("spam")
9 CALL (1)
>> 10 CACHE
11 CACHE
12 CACHE
13 LOAD_GLOBAL (3, NULL + StopAsyncIteration)
14 CACHE
15 CACHE
16 CACHE
17 CACHE
18 LOAD_CONST ("ham")
19 CALL (1)
20 CACHE
21 CACHE
22 CACHE
23 BUILD_TUPLE (2)
24 GET_ITER
25 FOR_ITER (71)
26 CACHE
27 STORE_FAST (0, stop_exc)
3 28 LOAD_GLOBAL (4, self)
29 CACHE
30 CACHE
31 CACHE
>> 32 CACHE
33 LOAD_ATTR (7, subTest, method=true)
34 CACHE
35 CACHE
36 CACHE
37 CACHE
38 CACHE
39 CACHE
40 CACHE
41 CACHE
42 CACHE
43 LOAD_GLOBAL (9, NULL + type)
44 CACHE
>> 45 CACHE
46 CACHE
47 CACHE
48 LOAD_FAST_BORROW (0, stop_exc)
49 CALL (1)
50 CACHE
51 CACHE
52 CACHE
53 LOAD_CONST (("type"))
54 CALL_KW (1)
55 CACHE
56 CACHE
57 CACHE
58 COPY (1)
59 LOAD_SPECIAL (__exit__)
60 SWAP (2)
61 SWAP (3)
62 LOAD_SPECIAL (__enter__)
63 CALL (0)
64 CACHE
65 CACHE
66 CACHE
67 POP_TOP
4 68 NOP
5 69 LOAD_GLOBAL (11, NULL + egg)
70 CACHE
>> 71 CACHE
72 CACHE
73 CACHE
74 CALL (0)
75 CACHE
76 CACHE
77 CACHE
78 COPY (1)
79 LOAD_SPECIAL (__aexit__)
80 SWAP (2)
81 SWAP (3)
82 LOAD_SPECIAL (__aenter__)
83 CALL (0)
84 CACHE
85 CACHE
86 CACHE
87 GET_AWAITABLE (1)
88 LOAD_CONST (None)
89 SEND (3)
90 CACHE
91 YIELD_VALUE (1)
92 RESUME (3)
93 JUMP_BACKWARD_NO_INTERRUPT(5)
94 END_SEND
95 POP_TOP
6 96 LOAD_FAST_BORROW (0, stop_exc)
97 RAISE_VARARGS (Raise)
2 98 END_FOR
99 POP_ITER
100 LOAD_CONST (None)
101 RETURN_VALUE
5 102 CLEANUP_THROW
103 JUMP_BACKWARD_NO_INTERRUPT(10)
104 PUSH_EXC_INFO
105 WITH_EXCEPT_START
106 GET_AWAITABLE (2)
107 LOAD_CONST (None)
108 SEND (4)
109 CACHE
110 YIELD_VALUE (1)
111 RESUME (3)
112 JUMP_BACKWARD_NO_INTERRUPT(5)
113 CLEANUP_THROW
114 END_SEND
115 TO_BOOL
116 CACHE
117 CACHE
118 CACHE
119 POP_JUMP_IF_TRUE (2)
120 CACHE
121 NOT_TAKEN
122 RERAISE (2)
123 POP_TOP
124 POP_EXCEPT
125 POP_TOP
126 POP_TOP
127 POP_TOP
128 JUMP_FORWARD (3)
129 COPY (3)
130 POP_EXCEPT
131 RERAISE (1)
132 NOP
10 133 LOAD_GLOBAL (4, self)
134 CACHE
135 CACHE
136 CACHE
137 CACHE
138 LOAD_ATTR (13, fail, method=true)
139 CACHE
140 CACHE
141 CACHE
142 CACHE
143 CACHE
144 CACHE
145 CACHE
146 CACHE
147 CACHE
148 LOAD_FAST (0, stop_exc)
149 FORMAT_SIMPLE
150 LOAD_CONST (" was suppressed")
151 BUILD_STRING (2)
152 CALL (1)
153 CACHE
154 CACHE
155 CACHE
156 POP_TOP
157 JUMP_FORWARD (45)
158 PUSH_EXC_INFO
7 159 LOAD_GLOBAL (14, Exception)
160 CACHE
161 CACHE
162 CACHE
163 CACHE
164 CHECK_EXC_MATCH
165 POP_JUMP_IF_FALSE (32)
166 CACHE
167 NOT_TAKEN
168 STORE_FAST (1, ex)
8 169 LOAD_GLOBAL (4, self)
170 CACHE
171 CACHE
172 CACHE
173 CACHE
174 LOAD_ATTR (17, assertIs, method=true)
175 CACHE
176 CACHE
177 CACHE
178 CACHE
179 CACHE
180 CACHE
181 CACHE
182 CACHE
183 CACHE
184 LOAD_FAST_LOAD_FAST (ex, stop_exc)
185 CALL (2)
186 CACHE
187 CACHE
>> 188 CACHE
189 POP_TOP
190 POP_EXCEPT
191 LOAD_CONST (None)
192 STORE_FAST (1, ex)
193 DELETE_FAST (1, ex)
194 JUMP_FORWARD (8)
195 LOAD_CONST (None)
196 STORE_FAST (1, ex)
197 DELETE_FAST (1, ex)
198 RERAISE (1)
7 199 RERAISE (0)
200 COPY (3)
201 POP_EXCEPT
202 RERAISE (1)
3 203 LOAD_CONST (None)
204 LOAD_CONST (None)
>> 205 LOAD_CONST (None)
206 CALL (3)
207 CACHE
208 CACHE
209 CACHE
210 POP_TOP
211 JUMP_BACKWARD (188)
212 CACHE
213 PUSH_EXC_INFO
214 WITH_EXCEPT_START
215 TO_BOOL
216 CACHE
217 CACHE
218 CACHE
219 POP_JUMP_IF_TRUE (2)
220 CACHE
221 NOT_TAKEN
222 RERAISE (2)
223 POP_TOP
224 POP_EXCEPT
225 POP_TOP
226 POP_TOP
227 POP_TOP
228 JUMP_BACKWARD (205)
229 CACHE
230 COPY (3)
231 POP_EXCEPT
232 RERAISE (1)
233 CALL_INTRINSIC_1 (StopIterationError)
234 RERAISE (1)
2 MAKE_FUNCTION
3 STORE_NAME (0, test)
4 LOAD_CONST (None)
5 RETURN_VALUE

View File

@@ -1,14 +0,0 @@
---
source: compiler/src/compile.rs
expression: "compile_exec(\"\\\nif True and False and False:\n pass\n\")"
---
1 0 LoadConst (True)
1 PopJumpIfFalse (6)
2 LoadConst (False)
3 PopJumpIfFalse (6)
4 LoadConst (False)
5 PopJumpIfFalse (6)
2 >> 6 LoadConst (None)
7 ReturnValue

View File

@@ -1,16 +0,0 @@
---
source: compiler/src/compile.rs
expression: "compile_exec(\"\\\nif (True and False) or (False and True):\n pass\n\")"
---
1 0 LoadConst (True)
1 PopJumpIfFalse (4)
2 LoadConst (False)
3 PopJumpIfTrue (8)
>> 4 LoadConst (False)
5 PopJumpIfFalse (8)
6 LoadConst (True)
7 PopJumpIfFalse (8)
2 >> 8 LoadConst (None)
9 ReturnValue

View File

@@ -1,14 +0,0 @@
---
source: compiler/src/compile.rs
expression: "compile_exec(\"\\\nif True or False or False:\n pass\n\")"
---
1 0 LoadConst (True)
1 PopJumpIfTrue (6)
2 LoadConst (False)
3 PopJumpIfTrue (6)
4 LoadConst (False)
5 PopJumpIfFalse (6)
2 >> 6 LoadConst (None)
7 ReturnValue

View File

@@ -1151,65 +1151,6 @@ impl<C: Constant> CodeObject<C> {
label_targets
}
fn display_inner(
&self,
f: &mut fmt::Formatter<'_>,
expand_code_objects: bool,
level: usize,
) -> fmt::Result {
let label_targets = self.label_targets();
let line_digits = (3).max(self.locations.last().unwrap().0.line.digits().get());
let offset_digits = (4).max(1 + self.instructions.len().ilog10() as usize);
let mut last_line = OneIndexed::MAX;
let mut arg_state = OpArgState::default();
for (offset, &instruction) in self.instructions.iter().enumerate() {
let (instruction, arg) = arg_state.get(instruction);
// optional line number
let line = self.locations[offset].0.line;
if line != last_line {
if last_line != OneIndexed::MAX {
writeln!(f)?;
}
last_line = line;
write!(f, "{line:line_digits$}")?;
} else {
for _ in 0..line_digits {
write!(f, " ")?;
}
}
write!(f, " ")?;
// level indent
for _ in 0..level {
write!(f, " ")?;
}
// arrow and offset
let arrow = if label_targets.contains(&Label::from_u32(offset as u32)) {
">>"
} else {
" "
};
write!(f, "{arrow} {offset:offset_digits$} ")?;
// instruction
instruction.fmt_dis(arg, f, self, expand_code_objects, 21, level)?;
writeln!(f)?;
}
Ok(())
}
/// Recursively display this CodeObject
pub fn display_expand_code_objects(&self) -> impl fmt::Display + '_ {
struct Display<'a, C: Constant>(&'a CodeObject<C>);
impl<C: Constant> fmt::Display for Display<'_, C> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.display_inner(f, true, 1)
}
}
Display(self)
}
/// Map this CodeObject to one that holds a Bag::Constant
pub fn map_bag<Bag: ConstantBag>(self, bag: Bag) -> CodeObject<Bag::Constant> {
let map_names = |names: Box<[C::Name]>| {
@@ -1279,19 +1220,6 @@ impl<C: Constant> CodeObject<C> {
}
}
impl<C: Constant> fmt::Display for CodeObject<C> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.display_inner(f, false, 1)?;
for constant in &*self.constants {
if let BorrowedConstant::Code { code } = constant.borrow_constant() {
writeln!(f, "\nDisassembly of {code:?}")?;
code.fmt(f)?;
}
}
Ok(())
}
}
pub trait InstrDisplayContext {
type Constant: Constant;

View File

@@ -1,14 +1,11 @@
use core::{fmt, marker::PhantomData};
use crate::{
bytecode::{
BorrowedConstant, Constant, InstrDisplayContext,
oparg::{
self, BinaryOperator, BuildSliceArgCount, CommonConstant, ComparisonOperator,
ConvertValueOparg, IntrinsicFunction1, IntrinsicFunction2, Invert, Label, LoadAttr,
LoadSuperAttr, MakeFunctionFlag, NameIdx, OpArg, OpArgByte, OpArgType, RaiseKind,
SpecialMethod, UnpackExArgs,
},
bytecode::oparg::{
self, BinaryOperator, BuildSliceArgCount, CommonConstant, ComparisonOperator,
ConvertValueOparg, IntrinsicFunction1, IntrinsicFunction2, Invert, Label, LoadAttr,
LoadSuperAttr, MakeFunctionFlag, NameIdx, OpArg, OpArgByte, OpArgType, RaiseKind,
SpecialMethod, UnpackExArgs,
},
marshal::MarshalError,
};
@@ -1025,262 +1022,6 @@ impl InstructionMetadata for Instruction {
// In CPython 3.14 the metadata-based stack_effect is the same for both
// fallthrough and branch paths for all real instructions.
// Only pseudo-instructions (SETUP_*) differ — see PseudoInstruction.
#[allow(clippy::too_many_arguments)]
fn fmt_dis(
&self,
arg: OpArg,
f: &mut fmt::Formatter<'_>,
ctx: &impl InstrDisplayContext,
expand_code_objects: bool,
pad: usize,
level: usize,
) -> fmt::Result {
let opcode = self.opcode();
macro_rules! w {
// No oparg. Show only opcode name.
() => {
write!(f, "{}", opcode)
};
// Oparg needs to be passed into a function.
($map:ident = $arg_marker:expr) => {{
let arg = $arg_marker.get(arg);
write!(f, "{:pad$}({}, {})", opcode, arg, $map(arg))
}};
// Oparg to be shown via `fmt::Display`
($arg_marker:expr) => {
write!(f, "{:pad$}({})", opcode, $arg_marker.get(arg))
};
// Oparg to be shown via `fmt::Debug`
(?$arg_marker:expr) => {
write!(f, "{:pad$}({:?})", opcode, $arg_marker.get(arg))
};
}
let varname = |var_num: oparg::VarNum| ctx.get_varname(var_num);
let name = |i: u32| ctx.get_name(i as usize);
let cell_name = |i: oparg::VarNum| ctx.get_localsplus_name(i);
match self {
Self::BinarySlice => w!(),
Self::BinaryOp { op } => w!(op),
Self::BinaryOpInplaceAddUnicode => w!(),
Self::BuildList { count } => w!(count),
Self::BuildMap { count } => w!(count),
Self::BuildSet { count } => w!(count),
Self::BuildSlice { argc } => w!(?argc),
Self::BuildString { count } => w!(count),
Self::BuildTuple { count } => w!(count),
Self::Call { argc } => w!(argc),
Self::CallFunctionEx => w!(),
Self::CallKw { argc } => w!(argc),
Self::CallIntrinsic1 { func } => w!(?func),
Self::CallIntrinsic2 { func } => w!(?func),
Self::Cache => w!(),
Self::CheckEgMatch => w!(),
Self::CheckExcMatch => w!(),
Self::CleanupThrow => w!(),
Self::CompareOp { opname } => {
let op = opname.get(arg);
if u32::from(arg) & oparg::COMPARE_OP_BOOL_MASK != 0 {
write!(f, "{:pad$}(bool({}))", opcode, op)
} else {
write!(f, "{:pad$}({})", opcode, op)
}
}
Self::ContainsOp { invert } => w!(invert),
Self::ConvertValue { oparg } => {
let oparg = oparg.get(arg);
write!(f, "{:pad$} {} ({})", opcode, oparg.as_u8(), oparg)
}
Self::Copy { i } => w!(i),
Self::CopyFreeVars { n } => w!(n),
Self::DeleteAttr { namei } => w!(name = namei),
Self::DeleteDeref { i } => w!(cell_name = i),
Self::DeleteFast { var_num } => w!(varname = var_num),
Self::DeleteGlobal { namei } => w!(name = namei),
Self::DeleteName { namei } => w!(name = namei),
Self::DeleteSubscr => w!(),
Self::DictMerge { i } => w!(i),
Self::DictUpdate { i } => w!(i),
Self::EndAsyncFor => w!(),
Self::EndSend => w!(),
Self::ExtendedArg => w!(Arg::<u32>::marker()),
Self::ExitInitCheck => w!(),
Self::ForIter { delta } => w!(delta),
Self::FormatSimple => w!(),
Self::FormatWithSpec => w!(),
Self::GetAIter => w!(),
Self::GetANext => w!(),
Self::GetAwaitable { r#where } => w!(r#where),
Self::Reserved => w!(),
Self::GetIter => w!(),
Self::GetLen => w!(),
Self::ImportFrom { namei } => w!(name = namei),
Self::ImportName { namei } => w!(name = namei),
Self::InterpreterExit => w!(),
Self::IsOp { invert } => w!(invert),
Self::JumpBackward { delta } => w!(delta),
Self::JumpBackwardNoInterrupt { delta } => w!(delta),
Self::JumpForward { delta } => w!(delta),
Self::ListAppend { i } => w!(i),
Self::ListExtend { i } => w!(i),
Self::LoadAttr { namei } => {
let oparg = namei.get(arg);
let oparg_u32 = u32::from(oparg);
let attr_name = name(oparg.name_idx());
if oparg.is_method() {
write!(f, "{opcode:pad$}({oparg_u32}, {attr_name}, method=true)",)
} else {
write!(f, "{opcode:pad$}({oparg_u32}, {attr_name})")
}
}
Self::LoadBuildClass => w!(),
Self::LoadCommonConstant { idx } => w!(?idx),
Self::LoadFromDictOrDeref { i } => w!(cell_name = i),
Self::LoadConst { consti } => {
let value = ctx.get_constant(consti.get(arg));
match value.borrow_constant() {
BorrowedConstant::Code { code } if expand_code_objects => {
write!(f, "{opcode:pad$}({code:?}):")?;
code.display_inner(f, true, level + 1)?;
Ok(())
}
c => {
write!(f, "{opcode:pad$}(")?;
c.fmt_display(f)?;
write!(f, ")")
}
}
}
Self::LoadSmallInt { i } => w!(i),
Self::LoadDeref { i } => w!(cell_name = i),
Self::LoadFast { var_num } => w!(varname = var_num),
Self::LoadFastAndClear { var_num } => w!(varname = var_num),
Self::LoadFastBorrow { var_num } => w!(varname = var_num),
Self::LoadFastCheck { var_num } => w!(varname = var_num),
Self::LoadFastLoadFast { var_nums } => {
let oparg = var_nums.get(arg);
let (idx1, idx2) = oparg.indexes();
let name1 = varname(idx1);
let name2 = varname(idx2);
write!(f, "{:pad$}({}, {})", opcode, name1, name2)
}
Self::LoadFastBorrowLoadFastBorrow { var_nums } => {
let oparg = var_nums.get(arg);
let (idx1, idx2) = oparg.indexes();
let name1 = varname(idx1);
let name2 = varname(idx2);
write!(f, "{:pad$}({}, {})", opcode, name1, name2)
}
Self::LoadFromDictOrGlobals { i } => w!(name = i),
Self::LoadGlobal { namei } => {
let oparg = namei.get(arg);
let name_idx = oparg >> 1;
if (oparg & 1) != 0 {
write!(f, "{:pad$}({}, NULL + {})", opcode, oparg, name(name_idx))
} else {
write!(f, "{:pad$}({}, {})", opcode, oparg, name(name_idx))
}
}
Self::LoadGlobalBuiltin => {
let oparg = u32::from(arg);
let name_idx = oparg >> 1;
if (oparg & 1) != 0 {
write!(f, "{:pad$}({}, NULL + {})", opcode, oparg, name(name_idx))
} else {
write!(f, "{:pad$}({}, {})", opcode, oparg, name(name_idx))
}
}
Self::LoadGlobalModule => {
let oparg = u32::from(arg);
let name_idx = oparg >> 1;
if (oparg & 1) != 0 {
write!(f, "{:pad$}({}, NULL + {})", opcode, oparg, name(name_idx))
} else {
write!(f, "{:pad$}({}, {})", opcode, oparg, name(name_idx))
}
}
Self::LoadLocals => w!(),
Self::LoadName { namei } => w!(name = namei),
Self::LoadSpecial { method } => w!(method),
Self::LoadSuperAttr { namei } => {
let oparg = namei.get(arg);
write!(
f,
"{:pad$}({}, {}, method={}, class={})",
opcode,
u32::from(oparg),
name(oparg.name_idx()),
oparg.is_load_method(),
oparg.has_class()
)
}
Self::MakeCell { i } => w!(cell_name = i),
Self::MakeFunction => w!(),
Self::MapAdd { i } => w!(i),
Self::MatchClass { count } => w!(count),
Self::MatchKeys => w!(),
Self::MatchMapping => w!(),
Self::MatchSequence => w!(),
Self::Nop => w!(),
Self::NotTaken => w!(),
Self::PopExcept => w!(),
Self::PopJumpIfFalse { delta } => w!(delta),
Self::PopJumpIfNone { delta } => w!(delta),
Self::PopJumpIfNotNone { delta } => w!(delta),
Self::PopJumpIfTrue { delta } => w!(delta),
Self::PopTop => w!(),
Self::EndFor => w!(),
Self::PopIter => w!(),
Self::PushExcInfo => w!(),
Self::PushNull => w!(),
Self::RaiseVarargs { argc } => w!(?argc),
Self::Reraise { depth } => w!(depth),
Self::Resume { context } => w!(context),
Self::ReturnValue => w!(),
Self::ReturnGenerator => w!(),
Self::Send { delta } => w!(delta),
Self::SetAdd { i } => w!(i),
Self::SetFunctionAttribute { flag } => w!(?flag),
Self::SetupAnnotations => w!(),
Self::SetUpdate { i } => w!(i),
Self::StoreAttr { namei } => w!(name = namei),
Self::StoreDeref { i } => w!(cell_name = i),
Self::StoreFast { var_num } => w!(varname = var_num),
Self::StoreFastLoadFast { var_nums } => {
let oparg = var_nums.get(arg);
let (store_idx, load_idx) = oparg.indexes();
write!(f, "{:pad$}({}, {})", opcode, store_idx, load_idx)
}
Self::StoreFastStoreFast { var_nums } => {
let oparg = var_nums.get(arg);
let (idx1, idx2) = oparg.indexes();
write!(f, "{:pad$}({}, {})", opcode, varname(idx1), varname(idx2))
}
Self::StoreGlobal { namei } => w!(name = namei),
Self::StoreName { namei } => w!(name = namei),
Self::StoreSlice => w!(),
Self::StoreSubscr => w!(),
Self::Swap { i } => w!(i),
Self::ToBool => w!(),
Self::UnpackEx { counts } => w!(counts),
Self::UnpackSequence { count } => w!(count),
Self::WithExceptStart => w!(),
Self::UnaryInvert => w!(),
Self::UnaryNegative => w!(),
Self::UnaryNot => w!(),
Self::YieldValue { arg } => w!(arg),
Self::GetYieldFromIter => w!(),
Self::BuildTemplate => w!(),
Self::BuildInterpolation { format } => w!(format),
_ => w!(),
}
}
}
define_opcodes!(
@@ -1381,18 +1122,6 @@ impl InstructionMetadata for PseudoInstruction {
fn is_unconditional_jump(&self) -> bool {
matches!(self, Self::Jump { .. } | Self::JumpNoInterrupt { .. })
}
fn fmt_dis(
&self,
_arg: OpArg,
_f: &mut fmt::Formatter<'_>,
_ctx: &impl InstrDisplayContext,
_expand_code_objects: bool,
_pad: usize,
_level: usize,
) -> fmt::Result {
unimplemented!()
}
}
#[derive(Clone, Copy, Debug)]
@@ -1476,16 +1205,6 @@ impl InstructionMetadata for AnyInstruction {
inst_either!(fn stack_effect_jump(&self, oparg: u32) -> i32);
inst_either!(fn stack_effect_info(&self, oparg: u32) -> StackEffect);
inst_either!(fn fmt_dis(
&self,
arg: OpArg,
f: &mut fmt::Formatter<'_>,
ctx: &impl InstrDisplayContext,
expand_code_objects: bool,
pad: usize,
level: usize
) -> fmt::Result);
}
impl AnyInstruction {
@@ -1715,21 +1434,6 @@ pub trait InstructionMetadata {
fn stack_effect_jump(&self, oparg: u32) -> i32 {
self.stack_effect(oparg)
}
#[allow(clippy::too_many_arguments)]
fn fmt_dis(
&self,
arg: OpArg,
f: &mut fmt::Formatter<'_>,
ctx: &impl InstrDisplayContext,
expand_code_objects: bool,
pad: usize,
level: usize,
) -> fmt::Result;
fn display(&self, arg: OpArg, ctx: &impl InstrDisplayContext) -> impl fmt::Display {
fmt::from_fn(move |f| self.fmt_dis(arg, f, ctx, false, 0, 0))
}
}
#[derive(Copy, Clone)]

View File

@@ -165,5 +165,10 @@ features = [
[target.'cfg(target_os = "macos")'.dependencies]
system-configuration = { workspace = true }
[dev-dependencies]
insta = { workspace = true }
rustpython-pylib = { workspace = true, features = [ "freeze-stdlib" ] }
[lints]
workspace = true

View File

@@ -255,3 +255,155 @@ mod _opcode {
vm.ctx.none()
}
}
#[cfg(test)]
mod tests {
use crate::vm::{self, compiler::Mode};
macro_rules! assert_dis_snapshot {
($value:expr) => {
insta::assert_snapshot!(dis($value))
};
}
/// Returns the [`dis.dis`](https://docs.python.org/3/library/dis.html#dis.dis) output.
///
/// # Notes
///
/// Memory addresses in the output are replaced with `0xdeadbeef` for consistency.
fn dis(source: &str) -> String {
let fname = String::from("<?>");
let builder = vm::Interpreter::builder(Default::default());
let stdlib_defs = crate::stdlib_module_defs(&builder.ctx);
let interp = builder
.add_native_modules(&stdlib_defs)
.add_frozen_modules(rustpython_pylib::FROZEN_STDLIB)
.build();
interp.enter(|vm| {
let scope = vm.new_scope_with_builtins();
let code_obj = vm
.compile(source.trim(), Mode::Exec, fname.clone())
.map_err(|err| vm.new_syntax_error(&err, Some(source)))
.unwrap();
scope.globals.set_item("code", code_obj.into(), vm).unwrap();
let py_source = r#"
import dis
import io
import re
import sys
old_stdout = sys.stdout
sys.stdout = buf = io.StringIO()
dis.dis(code)
sys.stdout = old_stdout
tmp_output = buf.getvalue()
# constant mem address
output = re.sub(r'(<code object \w+ at )0x[0-9a-fA-F]+', r'\g<1>0xdeadbeef', tmp_output)
"#;
let py_code_obj = vm
.compile(py_source, Mode::Exec, fname)
.map_err(|err| vm.new_syntax_error(&err, Some(py_source)))
.unwrap();
vm.run_code_obj(py_code_obj, scope.clone()).unwrap();
let py_output = scope.globals.get_item("output", vm).unwrap();
py_output.str(vm).unwrap().to_string()
})
}
#[test]
fn test_if_ors() {
assert_dis_snapshot!(
r#"
if True or False or False:
pass
"#
)
}
#[test]
fn test_if_ands() {
assert_dis_snapshot!(
r#"
if True and False and False:
pass
"#
)
}
#[test]
fn test_if_mixed() {
assert_dis_snapshot!(
r#"
if (True and False) or (False and True):
pass
"#
)
}
#[test]
fn test_nested_bool_op() {
assert_dis_snapshot!(
r#"
x = Test() and False or False
"#
)
}
#[test]
fn test_const_no_op() {
assert_dis_snapshot!(
r#"
x = not True
"#
)
}
#[test]
fn test_constant_true_if_pass_keeps_line_anchor_nop() {
assert_dis_snapshot!(
r#"
if 1:
pass
"#
)
}
#[test]
fn test_nested_double_async_with() {
assert_dis_snapshot!(
r#"
async def test():
for stop_exc in (StopIteration('spam'), StopAsyncIteration('ham')):
with self.subTest(type=type(stop_exc)):
try:
async with egg():
raise stop_exc
except Exception as ex:
self.assertIs(ex, stop_exc)
else:
self.fail(f'{stop_exc} was suppressed')
"#
)
}
#[test]
fn test_bare_function_annotations_check_attribute_and_subscript_expressions() {
assert_dis_snapshot!(
r#"
def f(one: int):
int.new_attr: int
[list][0].new_attr: [int, str]
my_lst = [1]
my_lst[one]: int
return my_lst
"#
)
}
}

View File

@@ -0,0 +1,52 @@
---
source: crates/stdlib/src/_opcode.rs
expression: "dis(r#\"\ndef f(one: int):\n int.new_attr: int\n [list][0].new_attr: [int, str]\n my_lst = [1]\n my_lst[one]: int\n return my_lst\n\"#)"
---
0 RESUME 0
1 LOAD_CONST 0 (<code object __annotate__ at 0xdeadbeef file "<?>", line 1>)
MAKE_FUNCTION
LOAD_CONST 1 (<code object f at 0xdeadbeef file "<?>", line 1>)
MAKE_FUNCTION
SET_FUNCTION_ATTRIBUTE 16 (annotate)
STORE_NAME 0 (f)
LOAD_CONST 2 (None)
RETURN_VALUE
Disassembly of <code object __annotate__ at 0xdeadbeef file "<?>", line 1>:
1 RESUME 0
LOAD_FAST_BORROW 0 (format)
LOAD_SMALL_INT 2
COMPARE_OP 132 (>)
POP_JUMP_IF_FALSE 3 (to L1)
NOT_TAKEN
LOAD_COMMON_CONSTANT 1 (NotImplementedError)
RAISE_VARARGS 1
L1: LOAD_CONST 1 ('one')
LOAD_GLOBAL 0 (int)
BUILD_MAP 1
RETURN_VALUE
Disassembly of <code object f at 0xdeadbeef file "<?>", line 1>:
1 RESUME 0
2 LOAD_GLOBAL 0 (int)
POP_TOP
3 LOAD_GLOBAL 2 (list)
BUILD_LIST 1
LOAD_SMALL_INT 0
BINARY_OP 26 ([])
POP_TOP
4 LOAD_SMALL_INT 1
BUILD_LIST 1
STORE_FAST 1 (my_lst)
5 LOAD_FAST_BORROW 1 (my_lst)
POP_TOP
LOAD_FAST_BORROW 0 (one)
POP_TOP
6 LOAD_FAST_BORROW 1 (my_lst)
RETURN_VALUE

View File

@@ -0,0 +1,10 @@
---
source: crates/stdlib/src/_opcode.rs
expression: x = not True
---
0 RESUME 0
1 LOAD_CONST 2 (False)
STORE_NAME 0 (x)
LOAD_CONST 1 (None)
RETURN_VALUE

View File

@@ -0,0 +1,10 @@
---
source: crates/stdlib/src/_opcode.rs
expression: "if 1:\n pass"
---
0 RESUME 0
1 NOP
2 LOAD_CONST 1 (None)
RETURN_VALUE

View File

@@ -0,0 +1,8 @@
---
source: crates/stdlib/src/_opcode.rs
expression: "if True and False and False:\n pass"
---
0 RESUME 0
1 LOAD_CONST 1 (None)
RETURN_VALUE

View File

@@ -0,0 +1,8 @@
---
source: crates/stdlib/src/_opcode.rs
expression: "if (True and False) or (False and True):\n pass"
---
0 RESUME 0
1 LOAD_CONST 1 (None)
RETURN_VALUE

View File

@@ -0,0 +1,10 @@
---
source: crates/stdlib/src/_opcode.rs
expression: "if True or False or False:\n pass"
---
0 RESUME 0
1 NOP
2 LOAD_CONST 1 (None)
RETURN_VALUE

View File

@@ -0,0 +1,24 @@
---
source: crates/stdlib/src/_opcode.rs
expression: x = Test() and False or False
---
0 RESUME 0
1 LOAD_NAME 0 (Test)
PUSH_NULL
CALL 0
COPY 1
TO_BOOL
POP_JUMP_IF_FALSE 11 (to L1)
NOT_TAKEN
POP_TOP
LOAD_CONST 0 (False)
COPY 1
TO_BOOL
POP_JUMP_IF_TRUE 3 (to L2)
NOT_TAKEN
L1: POP_TOP
LOAD_CONST 0 (False)
L2: STORE_NAME 1 (x)
LOAD_CONST 1 (None)
RETURN_VALUE

View File

@@ -0,0 +1,183 @@
---
source: crates/stdlib/src/_opcode.rs
expression: "dis(r#\"\nasync def test():\n for stop_exc in (StopIteration('spam'), StopAsyncIteration('ham')):\n with self.subTest(type=type(stop_exc)):\n try:\n async with egg():\n raise stop_exc\n except Exception as ex:\n self.assertIs(ex, stop_exc)\n else:\n self.fail(f'{stop_exc} was suppressed')\n\"#)"
---
0 RESUME 0
1 LOAD_CONST 0 (<code object test at 0xdeadbeef file "<?>", line 1>)
MAKE_FUNCTION
STORE_NAME 0 (test)
LOAD_CONST 1 (None)
RETURN_VALUE
Disassembly of <code object test at 0xdeadbeef file "<?>", line 1>:
1 RETURN_GENERATOR
POP_TOP
RESUME 0
2 L1: LOAD_GLOBAL 1 (StopIteration + NULL)
LOAD_CONST 0 ('spam')
CALL 1
LOAD_GLOBAL 3 (StopAsyncIteration + NULL)
LOAD_CONST 1 ('ham')
CALL 1
BUILD_TUPLE 2
GET_ITER
L2: FOR_ITER 71 (to L11)
STORE_FAST 0 (stop_exc)
3 LOAD_GLOBAL 4 (self)
LOAD_ATTR 7 (subTest + NULL|self)
LOAD_GLOBAL 9 (type + NULL)
LOAD_FAST_BORROW 0 (stop_exc)
CALL 1
LOAD_CONST 2 (('type',))
CALL_KW 1
COPY 1
LOAD_SPECIAL 1 (__exit__)
SWAP 2
SWAP 3
LOAD_SPECIAL 0 (__enter__)
CALL 0
L3: POP_TOP
4 L4: NOP
5 L5: LOAD_GLOBAL 11 (egg + NULL)
CALL 0
COPY 1
LOAD_SPECIAL 3 (__aexit__)
SWAP 2
SWAP 3
LOAD_SPECIAL 2 (__aenter__)
CALL 0
GET_AWAITABLE 1
LOAD_CONST 3 (None)
L6: SEND 3 (to L9)
L7: YIELD_VALUE 1
L8: RESUME 3
JUMP_BACKWARD_NO_INTERRUPT 5 (to L6)
L9: END_SEND
L10: POP_TOP
6 LOAD_FAST_BORROW 0 (stop_exc)
RAISE_VARARGS 1
2 L11: END_FOR
POP_ITER
LOAD_CONST 3 (None)
RETURN_VALUE
5 L12: CLEANUP_THROW
L13: JUMP_BACKWARD_NO_INTERRUPT 10 (to L9)
L14: PUSH_EXC_INFO
WITH_EXCEPT_START
GET_AWAITABLE 2
LOAD_CONST 3 (None)
L15: SEND 4 (to L19)
L16: YIELD_VALUE 1
L17: RESUME 3
JUMP_BACKWARD_NO_INTERRUPT 5 (to L15)
L18: CLEANUP_THROW
L19: END_SEND
TO_BOOL
POP_JUMP_IF_TRUE 2 (to L20)
NOT_TAKEN
RERAISE 2
L20: POP_TOP
L21: POP_EXCEPT
POP_TOP
POP_TOP
POP_TOP
JUMP_FORWARD 3 (to L23)
L22: COPY 3
POP_EXCEPT
RERAISE 1
L23: NOP
10 L24: LOAD_GLOBAL 4 (self)
LOAD_ATTR 13 (fail + NULL|self)
LOAD_FAST 0 (stop_exc)
FORMAT_SIMPLE
LOAD_CONST 4 (' was suppressed')
BUILD_STRING 2
CALL 1
POP_TOP
JUMP_FORWARD 45 (to L31)
-- L25: PUSH_EXC_INFO
7 LOAD_GLOBAL 14 (Exception)
CHECK_EXC_MATCH
POP_JUMP_IF_FALSE 32 (to L29)
NOT_TAKEN
STORE_FAST 1 (ex)
8 L26: LOAD_GLOBAL 4 (self)
LOAD_ATTR 17 (assertIs + NULL|self)
LOAD_FAST_LOAD_FAST 16 (ex, stop_exc)
CALL 2
POP_TOP
L27: POP_EXCEPT
LOAD_CONST 3 (None)
STORE_FAST 1 (ex)
DELETE_FAST 1 (ex)
JUMP_FORWARD 8 (to L31)
-- L28: LOAD_CONST 3 (None)
STORE_FAST 1 (ex)
DELETE_FAST 1 (ex)
RERAISE 1
7 L29: RERAISE 0
-- L30: COPY 3
POP_EXCEPT
RERAISE 1
3 L31: LOAD_CONST 3 (None)
LOAD_CONST 3 (None)
LOAD_CONST 3 (None)
CALL 3
POP_TOP
JUMP_BACKWARD 188 (to L2)
L32: PUSH_EXC_INFO
WITH_EXCEPT_START
TO_BOOL
POP_JUMP_IF_TRUE 2 (to L33)
NOT_TAKEN
RERAISE 2
L33: POP_TOP
L34: POP_EXCEPT
POP_TOP
POP_TOP
POP_TOP
JUMP_BACKWARD 205 (to L2)
L35: COPY 3
POP_EXCEPT
RERAISE 1
-- L36: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR)
RERAISE 1
ExceptionTable:
L1 to L3 -> L36 [0] lasti
L3 to L4 -> L32 [3] lasti
L5 to L7 -> L25 [3]
L7 to L8 -> L12 [7]
L8 to L10 -> L25 [3]
L10 to L11 -> L14 [5] lasti
L11 to L12 -> L36 [0] lasti
L12 to L13 -> L25 [3]
L14 to L16 -> L22 [7] lasti
L16 to L17 -> L18 [10]
L17 to L21 -> L22 [7] lasti
L21 to L23 -> L25 [3]
L24 to L25 -> L32 [3] lasti
L25 to L26 -> L30 [4] lasti
L26 to L27 -> L28 [4] lasti
L27 to L28 -> L32 [3] lasti
L28 to L30 -> L30 [4] lasti
L30 to L31 -> L32 [3] lasti
L31 to L32 -> L36 [0] lasti
L32 to L34 -> L35 [5] lasti
L34 to L36 -> L36 [0] lasti

View File

@@ -1462,12 +1462,6 @@ impl PyCode {
}
}
impl fmt::Display for PyCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl ToPyObject for CodeObject {
fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
vm.ctx.new_code(self).into()

View File

@@ -1,90 +0,0 @@
//! This an example usage of the rustpython_compiler crate.
//! This program reads, parses, and compiles a file you provide
//! to RustPython bytecode, and then displays the output in the
//! `dis.dis` format.
//!
//! example usage:
//! $ cargo run --release --example dis demo*.py
#[macro_use]
extern crate log;
use core::error::Error;
use lexopt::ValueExt;
use rustpython_compiler as compiler;
use std::fs;
use std::path::{Path, PathBuf};
fn main() -> Result<(), lexopt::Error> {
env_logger::init();
let mut scripts = vec![];
let mut mode = compiler::Mode::Exec;
let mut expand_code_objects = true;
let mut optimize = 0;
let mut parser = lexopt::Parser::from_env();
while let Some(arg) = parser.next()? {
use lexopt::Arg::*;
match arg {
Long("help") | Short('h') => {
let bin_name = parser.bin_name().unwrap_or("dis");
println!(
"usage: {bin_name} <scripts...> [-m,--mode=exec|single|eval] [-x,--no-expand] [-O]"
);
println!(
"Compiles and disassembles python script files for viewing their bytecode."
);
return Ok(());
}
Value(x) => scripts.push(PathBuf::from(x)),
Long("mode") | Short('m') => {
mode = parser
.value()?
.parse_with(|s| s.parse::<compiler::Mode>().map_err(|e| e.to_string()))?
}
Long("no-expand") | Short('x') => expand_code_objects = false,
Short('O') => optimize += 1,
_ => return Err(arg.unexpected()),
}
}
if scripts.is_empty() {
return Err("expected at least one argument".into());
}
let opts = compiler::CompileOpts {
optimize,
debug_ranges: true,
};
for script in &scripts {
if script.exists() && script.is_file() {
let res = display_script(script, mode, opts, expand_code_objects);
if let Err(e) = res {
error!("Error while compiling {script:?}: {e}");
}
} else {
eprintln!("{script:?} is not a file.");
}
}
Ok(())
}
fn display_script(
path: &Path,
mode: compiler::Mode,
opts: compiler::CompileOpts,
expand_code_objects: bool,
) -> Result<(), Box<dyn Error>> {
let source = fs::read_to_string(path)?;
let code = compiler::compile(&source, mode, &path.to_string_lossy(), opts)?;
println!("{}:", path.display());
if expand_code_objects {
println!("{}", code.display_expand_code_objects());
} else {
println!("{code}");
}
Ok(())
}