mirror of
https://github.com/erg-lang/erg.git
synced 2025-09-29 12:24:45 +00:00
WIP
This commit is contained in:
parent
c58042c640
commit
c2b228d5d5
4 changed files with 186 additions and 115 deletions
|
@ -5,7 +5,7 @@
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
#![allow(non_camel_case_types)]
|
#![allow(non_camel_case_types)]
|
||||||
|
|
||||||
use crate::impl_display_from_debug;
|
use crate::{impl_display_from_debug, impl_u8_enum};
|
||||||
|
|
||||||
/// Based on Python opcodes.
|
/// Based on Python opcodes.
|
||||||
/// This is represented by u8.
|
/// This is represented by u8.
|
||||||
|
@ -13,11 +13,11 @@ use crate::impl_display_from_debug;
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum CommonOpcode {
|
pub enum CommonOpcode {
|
||||||
POP_TOP = 1,
|
POP_TOP = 1,
|
||||||
ROT_TWO = 2,
|
// ROT_TWO = 2,
|
||||||
ROT_THREE = 3,
|
// ROT_THREE = 3,
|
||||||
DUP_TOP = 4,
|
// DUP_TOP = 4,
|
||||||
DUP_TOP2 = 5,
|
// DUP_TOP2 = 5,
|
||||||
ROT_FOUR = 6,
|
// ROT_FOUR = 6,
|
||||||
NOP = 9,
|
NOP = 9,
|
||||||
UNARY_POSITIVE = 10,
|
UNARY_POSITIVE = 10,
|
||||||
UNARY_NEGATIVE = 11,
|
UNARY_NEGATIVE = 11,
|
||||||
|
@ -55,9 +55,9 @@ pub enum CommonOpcode {
|
||||||
JUMP_FORWARD = 110,
|
JUMP_FORWARD = 110,
|
||||||
JUMP_IF_FALSE_OR_POP = 111,
|
JUMP_IF_FALSE_OR_POP = 111,
|
||||||
JUMP_IF_TRUE_OR_POP = 112,
|
JUMP_IF_TRUE_OR_POP = 112,
|
||||||
JUMP_ABSOLUTE = 113,
|
// JUMP_ABSOLUTE = 113,
|
||||||
POP_JUMP_IF_FALSE = 114,
|
// POP_JUMP_IF_FALSE = 114,
|
||||||
POP_JUMP_IF_TRUE = 115,
|
// POP_JUMP_IF_TRUE = 115,
|
||||||
LOAD_GLOBAL = 116,
|
LOAD_GLOBAL = 116,
|
||||||
LOAD_FAST = 124,
|
LOAD_FAST = 124,
|
||||||
STORE_FAST = 125,
|
STORE_FAST = 125,
|
||||||
|
@ -82,11 +82,11 @@ impl TryFrom<u8> for CommonOpcode {
|
||||||
fn try_from(byte: u8) -> Result<Self, ()> {
|
fn try_from(byte: u8) -> Result<Self, ()> {
|
||||||
Ok(match byte {
|
Ok(match byte {
|
||||||
1 => POP_TOP,
|
1 => POP_TOP,
|
||||||
2 => ROT_TWO,
|
// 2 => ROT_TWO,
|
||||||
3 => ROT_THREE,
|
// 3 => ROT_THREE,
|
||||||
4 => DUP_TOP,
|
// 4 => DUP_TOP,
|
||||||
5 => DUP_TOP2,
|
// 5 => DUP_TOP2,
|
||||||
6 => ROT_FOUR,
|
// 6 => ROT_FOUR,
|
||||||
9 => NOP,
|
9 => NOP,
|
||||||
10 => UNARY_POSITIVE,
|
10 => UNARY_POSITIVE,
|
||||||
11 => UNARY_NEGATIVE,
|
11 => UNARY_NEGATIVE,
|
||||||
|
@ -122,9 +122,9 @@ impl TryFrom<u8> for CommonOpcode {
|
||||||
110 => JUMP_FORWARD,
|
110 => JUMP_FORWARD,
|
||||||
111 => JUMP_IF_FALSE_OR_POP,
|
111 => JUMP_IF_FALSE_OR_POP,
|
||||||
112 => JUMP_IF_TRUE_OR_POP,
|
112 => JUMP_IF_TRUE_OR_POP,
|
||||||
113 => JUMP_ABSOLUTE,
|
// 113 => JUMP_ABSOLUTE,
|
||||||
114 => POP_JUMP_IF_FALSE,
|
// 114 => POP_JUMP_IF_FALSE,
|
||||||
115 => POP_JUMP_IF_TRUE,
|
// 115 => POP_JUMP_IF_TRUE,
|
||||||
116 => LOAD_GLOBAL,
|
116 => LOAD_GLOBAL,
|
||||||
124 => LOAD_FAST,
|
124 => LOAD_FAST,
|
||||||
125 => STORE_FAST,
|
125 => STORE_FAST,
|
||||||
|
@ -154,3 +154,25 @@ impl CommonOpcode {
|
||||||
90 <= (*self as u8) && (*self as u8) < 220
|
90 <= (*self as u8) && (*self as u8) < 220
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl_u8_enum! {CompareOp;
|
||||||
|
LT = 0,
|
||||||
|
LE = 1,
|
||||||
|
EQ = 2,
|
||||||
|
NE = 3,
|
||||||
|
GT = 4,
|
||||||
|
GE = 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CompareOp {
|
||||||
|
fn show_op(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
CompareOp::LT => "<",
|
||||||
|
CompareOp::LE => "<=",
|
||||||
|
CompareOp::EQ => "==",
|
||||||
|
CompareOp::NE => "!=",
|
||||||
|
CompareOp::GT => ">",
|
||||||
|
CompareOp::GE => ">=",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -67,9 +67,8 @@ impl_u8_enum! {Opcode311;
|
||||||
JUMP_FORWARD = 110,
|
JUMP_FORWARD = 110,
|
||||||
JUMP_IF_FALSE_OR_POP = 111,
|
JUMP_IF_FALSE_OR_POP = 111,
|
||||||
JUMP_IF_TRUE_OR_POP = 112,
|
JUMP_IF_TRUE_OR_POP = 112,
|
||||||
JUMP_ABSOLUTE = 113,
|
POP_JUMP_FORWARD_IF_FALSE = 114,
|
||||||
POP_JUMP_IF_FALSE = 114,
|
POP_JUMP_FORWARD_IF_TRUE = 115,
|
||||||
POP_JUMP_IF_TRUE = 115,
|
|
||||||
LOAD_GLOBAL = 116,
|
LOAD_GLOBAL = 116,
|
||||||
IS_OP = 117,
|
IS_OP = 117,
|
||||||
CONTAINS_OP = 118,
|
CONTAINS_OP = 118,
|
||||||
|
@ -86,6 +85,7 @@ impl_u8_enum! {Opcode311;
|
||||||
LOAD_CLOSURE = 135,
|
LOAD_CLOSURE = 135,
|
||||||
LOAD_DEREF = 136,
|
LOAD_DEREF = 136,
|
||||||
STORE_DEREF = 137,
|
STORE_DEREF = 137,
|
||||||
|
JUMP_BACKWARD = 140,
|
||||||
CALL_FUNCTION_EX = 142,
|
CALL_FUNCTION_EX = 142,
|
||||||
EXTENDED_ARG = 144,
|
EXTENDED_ARG = 144,
|
||||||
LIST_APPEND = 145,
|
LIST_APPEND = 145,
|
||||||
|
|
|
@ -12,7 +12,7 @@ use erg_common::cache::CacheSet;
|
||||||
use erg_common::config::{ErgConfig, Input};
|
use erg_common::config::{ErgConfig, Input};
|
||||||
use erg_common::env::erg_std_path;
|
use erg_common::env::erg_std_path;
|
||||||
use erg_common::error::{ErrorDisplay, Location};
|
use erg_common::error::{ErrorDisplay, Location};
|
||||||
use erg_common::opcode::CommonOpcode;
|
use erg_common::opcode::{CommonOpcode, CompareOp};
|
||||||
use erg_common::opcode308::Opcode308;
|
use erg_common::opcode308::Opcode308;
|
||||||
use erg_common::opcode310::Opcode310;
|
use erg_common::opcode310::Opcode310;
|
||||||
use erg_common::opcode311::{BinOpCode, Opcode311};
|
use erg_common::opcode311::{BinOpCode, Opcode311};
|
||||||
|
@ -209,6 +209,15 @@ impl CodeGenerator {
|
||||||
self.stack_dec();
|
self.stack_dec();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn emit_compare_op(&mut self, op: CompareOp) {
|
||||||
|
self.write_instr(Opcode311::COMPARE_OP);
|
||||||
|
self.write_arg(op as usize);
|
||||||
|
self.stack_dec();
|
||||||
|
if self.py_version.minor >= Some(11) {
|
||||||
|
self.write_bytes(&[0; 4]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn jump_delta(&self, jump_to: usize) -> usize {
|
fn jump_delta(&self, jump_to: usize) -> usize {
|
||||||
if self.py_version.minor >= Some(10) {
|
if self.py_version.minor >= Some(10) {
|
||||||
|
@ -224,7 +233,7 @@ impl CodeGenerator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn edit_jump(&mut self, idx: usize, jump_to: usize) {
|
fn calc_edit_jump(&mut self, idx: usize, jump_to: usize) {
|
||||||
let arg = if self.py_version.minor >= Some(10) {
|
let arg = if self.py_version.minor >= Some(10) {
|
||||||
jump_to / 2
|
jump_to / 2
|
||||||
} else {
|
} else {
|
||||||
|
@ -498,8 +507,13 @@ impl CodeGenerator {
|
||||||
.unwrap_or_else(|| self.register_name(escaped));
|
.unwrap_or_else(|| self.register_name(escaped));
|
||||||
let instr = match name.kind {
|
let instr = match name.kind {
|
||||||
StoreLoadKind::Fast | StoreLoadKind::FastConst => LOAD_FAST,
|
StoreLoadKind::Fast | StoreLoadKind::FastConst => LOAD_FAST,
|
||||||
StoreLoadKind::Global | StoreLoadKind::GlobalConst =>
|
StoreLoadKind::Global | StoreLoadKind::GlobalConst => {
|
||||||
if self.py_version.minor >= Some(11) { LOAD_NAME } else { LOAD_GLOBAL },
|
if self.py_version.minor >= Some(11) {
|
||||||
|
LOAD_NAME
|
||||||
|
} else {
|
||||||
|
LOAD_GLOBAL
|
||||||
|
}
|
||||||
|
}
|
||||||
StoreLoadKind::Deref | StoreLoadKind::DerefConst => LOAD_DEREF,
|
StoreLoadKind::Deref | StoreLoadKind::DerefConst => LOAD_DEREF,
|
||||||
StoreLoadKind::Local | StoreLoadKind::LocalConst => LOAD_NAME,
|
StoreLoadKind::Local | StoreLoadKind::LocalConst => LOAD_NAME,
|
||||||
};
|
};
|
||||||
|
@ -1358,8 +1372,10 @@ impl CodeGenerator {
|
||||||
self.write_bytes(&[0; 8]);
|
self.write_bytes(&[0; 8]);
|
||||||
}
|
}
|
||||||
Opcode311::BINARY_OP => {
|
Opcode311::BINARY_OP => {
|
||||||
self.write_arg(0);
|
self.write_bytes(&[0; 2]);
|
||||||
self.write_arg(0);
|
}
|
||||||
|
Opcode311::COMPARE_OP => {
|
||||||
|
self.write_bytes(&[0; 4]);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
@ -1401,7 +1417,7 @@ impl CodeGenerator {
|
||||||
let cond = args.remove(0);
|
let cond = args.remove(0);
|
||||||
self.emit_expr(cond);
|
self.emit_expr(cond);
|
||||||
let idx_pop_jump_if_false = self.cur_block().lasti;
|
let idx_pop_jump_if_false = self.cur_block().lasti;
|
||||||
self.write_instr(POP_JUMP_IF_FALSE);
|
self.write_instr(Opcode310::POP_JUMP_IF_FALSE);
|
||||||
// cannot detect where to jump to at this moment, so put as 0
|
// cannot detect where to jump to at this moment, so put as 0
|
||||||
self.write_arg(0);
|
self.write_arg(0);
|
||||||
match args.remove(0) {
|
match args.remove(0) {
|
||||||
|
@ -1419,7 +1435,7 @@ impl CodeGenerator {
|
||||||
self.write_arg(0);
|
self.write_arg(0);
|
||||||
// else block
|
// else block
|
||||||
let idx_else_begin = self.cur_block().lasti;
|
let idx_else_begin = self.cur_block().lasti;
|
||||||
self.edit_jump(idx_pop_jump_if_false + 1, idx_else_begin);
|
self.calc_edit_jump(idx_pop_jump_if_false + 1, idx_else_begin);
|
||||||
match args.remove(0) {
|
match args.remove(0) {
|
||||||
Expr::Lambda(lambda) => {
|
Expr::Lambda(lambda) => {
|
||||||
// let params = self.gen_param_names(&lambda.params);
|
// let params = self.gen_param_names(&lambda.params);
|
||||||
|
@ -1431,15 +1447,19 @@ impl CodeGenerator {
|
||||||
}
|
}
|
||||||
let idx_jump_forward = idx_else_begin - 2;
|
let idx_jump_forward = idx_else_begin - 2;
|
||||||
let idx_end = self.cur_block().lasti;
|
let idx_end = self.cur_block().lasti;
|
||||||
self.edit_jump(idx_jump_forward + 1, idx_end - idx_jump_forward - 2);
|
self.calc_edit_jump(idx_jump_forward + 1, idx_end - idx_jump_forward - 2);
|
||||||
// FIXME: this is a hack to make sure the stack is balanced
|
// FIXME: this is a hack to make sure the stack is balanced
|
||||||
while self.cur_block().stack_len != init_stack_len + 1 {
|
while self.cur_block().stack_len != init_stack_len + 1 {
|
||||||
self.stack_dec();
|
self.stack_dec();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// no else block
|
// no else block
|
||||||
let idx_end = self.cur_block().lasti;
|
let idx_end = if self.py_version.minor >= Some(11) {
|
||||||
self.edit_jump(idx_pop_jump_if_false + 1, idx_end);
|
self.cur_block().lasti - idx_pop_jump_if_false
|
||||||
|
} else {
|
||||||
|
self.cur_block().lasti
|
||||||
|
};
|
||||||
|
self.calc_edit_jump(idx_pop_jump_if_false + 1, idx_end);
|
||||||
self.emit_load_const(ValueObj::None);
|
self.emit_load_const(ValueObj::None);
|
||||||
while self.cur_block().stack_len != init_stack_len + 1 {
|
while self.cur_block().stack_len != init_stack_len + 1 {
|
||||||
self.stack_dec();
|
self.stack_dec();
|
||||||
|
@ -1467,15 +1487,23 @@ impl CodeGenerator {
|
||||||
if self.cur_block().stack_len >= init_stack_len {
|
if self.cur_block().stack_len >= init_stack_len {
|
||||||
self.emit_pop_top();
|
self.emit_pop_top();
|
||||||
}
|
}
|
||||||
self.write_instr(JUMP_ABSOLUTE);
|
match self.py_version.minor {
|
||||||
let arg = if self.py_version.minor >= Some(10) {
|
Some(11) => {
|
||||||
idx_for_iter / 2
|
self.write_instr(Opcode311::JUMP_BACKWARD);
|
||||||
} else {
|
self.write_arg((self.cur_block().lasti - idx_for_iter + 2) / 2);
|
||||||
idx_for_iter
|
},
|
||||||
};
|
Some(10) => {
|
||||||
self.write_arg(arg);
|
self.write_instr(Opcode310::JUMP_ABSOLUTE);
|
||||||
|
self.write_arg(idx_for_iter / 2);
|
||||||
|
},
|
||||||
|
Some(8) => {
|
||||||
|
self.write_instr(Opcode308::JUMP_ABSOLUTE);
|
||||||
|
self.write_arg(idx_for_iter);
|
||||||
|
},
|
||||||
|
_ => todo!(),
|
||||||
|
}
|
||||||
let idx_end = self.cur_block().lasti;
|
let idx_end = self.cur_block().lasti;
|
||||||
self.edit_jump(idx_for_iter + 1, idx_end - idx_for_iter - 2);
|
self.calc_edit_jump(idx_for_iter + 1, idx_end - idx_for_iter - 2);
|
||||||
self.stack_dec();
|
self.stack_dec();
|
||||||
self.emit_load_const(ValueObj::None);
|
self.emit_load_const(ValueObj::None);
|
||||||
}
|
}
|
||||||
|
@ -1485,7 +1513,7 @@ impl CodeGenerator {
|
||||||
let cond = args.remove(0);
|
let cond = args.remove(0);
|
||||||
self.emit_expr(cond.clone());
|
self.emit_expr(cond.clone());
|
||||||
let idx_while = self.cur_block().lasti;
|
let idx_while = self.cur_block().lasti;
|
||||||
self.write_instr(POP_JUMP_IF_FALSE);
|
self.write_instr(Opcode310::POP_JUMP_IF_FALSE);
|
||||||
self.write_arg(0);
|
self.write_arg(0);
|
||||||
self.stack_dec();
|
self.stack_dec();
|
||||||
let lambda = enum_unwrap!(args.remove(0), Expr::Lambda);
|
let lambda = enum_unwrap!(args.remove(0), Expr::Lambda);
|
||||||
|
@ -1496,7 +1524,7 @@ impl CodeGenerator {
|
||||||
self.emit_pop_top();
|
self.emit_pop_top();
|
||||||
}
|
}
|
||||||
self.emit_expr(cond);
|
self.emit_expr(cond);
|
||||||
self.write_instr(POP_JUMP_IF_TRUE);
|
self.write_instr(Opcode310::POP_JUMP_IF_TRUE);
|
||||||
let arg = if self.py_version.minor >= Some(10) {
|
let arg = if self.py_version.minor >= Some(10) {
|
||||||
(idx_while + 2) / 2
|
(idx_while + 2) / 2
|
||||||
} else {
|
} else {
|
||||||
|
@ -1504,8 +1532,12 @@ impl CodeGenerator {
|
||||||
};
|
};
|
||||||
self.write_arg(arg);
|
self.write_arg(arg);
|
||||||
self.stack_dec();
|
self.stack_dec();
|
||||||
let idx_end = self.cur_block().lasti;
|
let idx_end = if self.py_version.minor >= Some(11) {
|
||||||
self.edit_jump(idx_while + 1, idx_end);
|
self.cur_block().lasti - idx_while
|
||||||
|
} else {
|
||||||
|
self.cur_block().lasti
|
||||||
|
};
|
||||||
|
self.calc_edit_jump(idx_while + 1, idx_end);
|
||||||
self.emit_load_const(ValueObj::None);
|
self.emit_load_const(ValueObj::None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1518,8 +1550,13 @@ impl CodeGenerator {
|
||||||
while let Some(expr) = args.try_remove(0) {
|
while let Some(expr) = args.try_remove(0) {
|
||||||
// パターンが複数ある場合引数を複製する、ただし最後はしない
|
// パターンが複数ある場合引数を複製する、ただし最後はしない
|
||||||
if len > 1 && !args.is_empty() {
|
if len > 1 && !args.is_empty() {
|
||||||
self.write_instr(DUP_TOP);
|
if self.py_version.minor >= Some(11) {
|
||||||
self.write_arg(0);
|
self.write_instr(Opcode311::COPY);
|
||||||
|
self.write_arg(1);
|
||||||
|
} else {
|
||||||
|
self.write_instr(Opcode310::DUP_TOP);
|
||||||
|
self.write_arg(0);
|
||||||
|
}
|
||||||
self.stack_inc();
|
self.stack_inc();
|
||||||
}
|
}
|
||||||
// compilerで型チェック済み(可読性が下がるため、matchでNamedは使えない)
|
// compilerで型チェック済み(可読性が下がるため、matchでNamedは使えない)
|
||||||
|
@ -1532,16 +1569,20 @@ impl CodeGenerator {
|
||||||
let pop_jump_points = self.emit_match_pattern(pat);
|
let pop_jump_points = self.emit_match_pattern(pat);
|
||||||
self.emit_frameless_block(lambda.body, Vec::new());
|
self.emit_frameless_block(lambda.body, Vec::new());
|
||||||
for pop_jump_point in pop_jump_points.into_iter() {
|
for pop_jump_point in pop_jump_points.into_iter() {
|
||||||
let idx = self.cur_block().lasti + 2;
|
let idx = if self.py_version.minor >= Some(11) {
|
||||||
self.edit_jump(pop_jump_point + 1, idx); // jump to POP_TOP
|
self.cur_block().lasti - pop_jump_point // - 2
|
||||||
|
} else {
|
||||||
|
self.cur_block().lasti + 2
|
||||||
|
};
|
||||||
|
self.calc_edit_jump(pop_jump_point + 1, idx); // jump to POP_TOP
|
||||||
absolute_jump_points.push(self.cur_block().lasti);
|
absolute_jump_points.push(self.cur_block().lasti);
|
||||||
self.write_instr(JUMP_ABSOLUTE); // jump to the end
|
self.write_instr(JUMP_FORWARD); // jump to the end
|
||||||
self.write_arg(0);
|
self.write_arg(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let lasti = self.cur_block().lasti;
|
let lasti = self.cur_block().lasti;
|
||||||
for absolute_jump_point in absolute_jump_points.into_iter() {
|
for absolute_jump_point in absolute_jump_points.into_iter() {
|
||||||
self.edit_jump(absolute_jump_point + 1, lasti);
|
self.calc_edit_jump(absolute_jump_point + 1, lasti - absolute_jump_point - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1559,11 +1600,11 @@ impl CodeGenerator {
|
||||||
ValueObj::from_str(t, lit.token.content).unwrap()
|
ValueObj::from_str(t, lit.token.content).unwrap()
|
||||||
};
|
};
|
||||||
self.emit_load_const(value);
|
self.emit_load_const(value);
|
||||||
self.write_instr(COMPARE_OP);
|
self.emit_compare_op(CompareOp::EQ);
|
||||||
self.write_arg(2); // ==
|
|
||||||
self.stack_dec();
|
|
||||||
pop_jump_points.push(self.cur_block().lasti);
|
pop_jump_points.push(self.cur_block().lasti);
|
||||||
self.write_instr(POP_JUMP_IF_FALSE); // jump to the next case
|
// in 3.11, POP_JUMP_IF_FALSE is replaced with POP_JUMP_FORWARD_IF_FALSE
|
||||||
|
// but the numbers are the same, only the way the jumping points are calculated is different.
|
||||||
|
self.write_instr(Opcode310::POP_JUMP_IF_FALSE); // jump to the next case
|
||||||
self.write_arg(0);
|
self.write_arg(0);
|
||||||
self.emit_pop_top();
|
self.emit_pop_top();
|
||||||
self.stack_dec();
|
self.stack_dec();
|
||||||
|
@ -1573,17 +1614,15 @@ impl CodeGenerator {
|
||||||
self.write_instr(Opcode310::MATCH_SEQUENCE);
|
self.write_instr(Opcode310::MATCH_SEQUENCE);
|
||||||
self.write_arg(0);
|
self.write_arg(0);
|
||||||
pop_jump_points.push(self.cur_block().lasti);
|
pop_jump_points.push(self.cur_block().lasti);
|
||||||
self.write_instr(POP_JUMP_IF_FALSE);
|
self.write_instr(Opcode310::POP_JUMP_IF_FALSE);
|
||||||
self.write_arg(0);
|
self.write_arg(0);
|
||||||
self.stack_dec();
|
self.stack_dec();
|
||||||
self.write_instr(Opcode310::GET_LEN);
|
self.write_instr(Opcode310::GET_LEN);
|
||||||
self.write_arg(0);
|
self.write_arg(0);
|
||||||
self.emit_load_const(len);
|
self.emit_load_const(len);
|
||||||
self.write_instr(COMPARE_OP);
|
self.emit_compare_op(CompareOp::EQ);
|
||||||
self.write_arg(2); // ==
|
|
||||||
self.stack_dec();
|
|
||||||
pop_jump_points.push(self.cur_block().lasti);
|
pop_jump_points.push(self.cur_block().lasti);
|
||||||
self.write_instr(POP_JUMP_IF_FALSE);
|
self.write_instr(Opcode310::POP_JUMP_IF_FALSE);
|
||||||
self.write_arg(0);
|
self.write_arg(0);
|
||||||
self.stack_dec();
|
self.stack_dec();
|
||||||
self.write_instr(Opcode310::UNPACK_SEQUENCE);
|
self.write_instr(Opcode310::UNPACK_SEQUENCE);
|
||||||
|
@ -1625,10 +1664,10 @@ impl CodeGenerator {
|
||||||
self.write_instr(POP_BLOCK);
|
self.write_instr(POP_BLOCK);
|
||||||
self.write_arg(0);
|
self.write_arg(0);
|
||||||
self.emit_load_const(ValueObj::None);
|
self.emit_load_const(ValueObj::None);
|
||||||
self.write_instr(DUP_TOP);
|
self.write_instr(Opcode310::DUP_TOP);
|
||||||
self.write_arg(0);
|
self.write_arg(0);
|
||||||
self.stack_inc();
|
self.stack_inc();
|
||||||
self.write_instr(DUP_TOP);
|
self.write_instr(Opcode310::DUP_TOP);
|
||||||
self.write_arg(0);
|
self.write_arg(0);
|
||||||
self.stack_inc();
|
self.stack_inc();
|
||||||
self.write_instr(Opcode310::CALL_FUNCTION);
|
self.write_instr(Opcode310::CALL_FUNCTION);
|
||||||
|
@ -1645,7 +1684,7 @@ impl CodeGenerator {
|
||||||
self.write_instr(Opcode310::WITH_EXCEPT_START);
|
self.write_instr(Opcode310::WITH_EXCEPT_START);
|
||||||
self.write_arg(0);
|
self.write_arg(0);
|
||||||
let idx_pop_jump_if_true = self.cur_block().lasti;
|
let idx_pop_jump_if_true = self.cur_block().lasti;
|
||||||
self.write_instr(POP_JUMP_IF_TRUE);
|
self.write_instr(Opcode310::POP_JUMP_IF_TRUE);
|
||||||
self.write_arg(0);
|
self.write_arg(0);
|
||||||
self.write_instr(Opcode310::RERAISE);
|
self.write_instr(Opcode310::RERAISE);
|
||||||
self.write_arg(1);
|
self.write_arg(1);
|
||||||
|
@ -1852,7 +1891,7 @@ impl CodeGenerator {
|
||||||
log!(info "entered {}", fn_name!());
|
log!(info "entered {}", fn_name!());
|
||||||
self.emit_expr(args.remove(0));
|
self.emit_expr(args.remove(0));
|
||||||
let pop_jump_point = self.cur_block().lasti;
|
let pop_jump_point = self.cur_block().lasti;
|
||||||
self.write_instr(POP_JUMP_IF_TRUE);
|
self.write_instr(Opcode310::POP_JUMP_IF_TRUE);
|
||||||
self.write_arg(0);
|
self.write_arg(0);
|
||||||
self.stack_dec();
|
self.stack_dec();
|
||||||
if self.py_version.minor >= Some(10) {
|
if self.py_version.minor >= Some(10) {
|
||||||
|
@ -1874,10 +1913,11 @@ impl CodeGenerator {
|
||||||
self.write_instr(RAISE_VARARGS);
|
self.write_instr(RAISE_VARARGS);
|
||||||
self.write_arg(1);
|
self.write_arg(1);
|
||||||
self.stack_dec();
|
self.stack_dec();
|
||||||
let idx = if self.py_version.minor >= Some(10) {
|
let idx = match self.py_version.minor {
|
||||||
self.cur_block().lasti / 2
|
Some(11) => (self.cur_block().lasti - pop_jump_point) / 2,
|
||||||
} else {
|
Some(10) => self.cur_block().lasti / 2,
|
||||||
self.cur_block().lasti
|
Some(_) => self.cur_block().lasti,
|
||||||
|
_ => todo!(),
|
||||||
};
|
};
|
||||||
self.edit_code(pop_jump_point + 1, idx);
|
self.edit_code(pop_jump_point + 1, idx);
|
||||||
}
|
}
|
||||||
|
|
|
@ -390,31 +390,31 @@ impl CodeObj {
|
||||||
tables += "Constants:\n";
|
tables += "Constants:\n";
|
||||||
}
|
}
|
||||||
for (i, obj) in self.consts.iter().enumerate() {
|
for (i, obj) in self.consts.iter().enumerate() {
|
||||||
writeln!(tables, " {}: {}", i, obj).unwrap();
|
writeln!(tables, " {i}: {obj}").unwrap();
|
||||||
}
|
}
|
||||||
if !self.names.is_empty() {
|
if !self.names.is_empty() {
|
||||||
tables += "Names:\n";
|
tables += "Names:\n";
|
||||||
}
|
}
|
||||||
for (i, name) in self.names.iter().enumerate() {
|
for (i, name) in self.names.iter().enumerate() {
|
||||||
writeln!(tables, " {}: {}", i, name).unwrap();
|
writeln!(tables, " {i}: {name}").unwrap();
|
||||||
}
|
}
|
||||||
if !self.varnames.is_empty() {
|
if !self.varnames.is_empty() {
|
||||||
tables += "Varnames:\n";
|
tables += "Varnames:\n";
|
||||||
}
|
}
|
||||||
for (i, varname) in self.varnames.iter().enumerate() {
|
for (i, varname) in self.varnames.iter().enumerate() {
|
||||||
writeln!(tables, " {}: {}", i, varname).unwrap();
|
writeln!(tables, " {i}: {varname}").unwrap();
|
||||||
}
|
}
|
||||||
if !self.cellvars.is_empty() {
|
if !self.cellvars.is_empty() {
|
||||||
tables += "Cellvars:\n";
|
tables += "Cellvars:\n";
|
||||||
}
|
}
|
||||||
for (i, cellvar) in self.cellvars.iter().enumerate() {
|
for (i, cellvar) in self.cellvars.iter().enumerate() {
|
||||||
writeln!(tables, " {}: {}", i, cellvar).unwrap();
|
writeln!(tables, " {i}: {cellvar}").unwrap();
|
||||||
}
|
}
|
||||||
if !self.freevars.is_empty() {
|
if !self.freevars.is_empty() {
|
||||||
tables += "Freevars:\n";
|
tables += "Freevars:\n";
|
||||||
}
|
}
|
||||||
for (i, freevar) in self.freevars.iter().enumerate() {
|
for (i, freevar) in self.freevars.iter().enumerate() {
|
||||||
writeln!(tables, " {}: {}\n", i, freevar).unwrap();
|
writeln!(tables, " {i}: {freevar}\n").unwrap();
|
||||||
}
|
}
|
||||||
tables
|
tables
|
||||||
}
|
}
|
||||||
|
@ -432,12 +432,12 @@ impl CodeObj {
|
||||||
for i in 0..32 {
|
for i in 0..32 {
|
||||||
if (self.flags & (1 << i)) != 0 {
|
if (self.flags & (1 << i)) != 0 {
|
||||||
let flag: CodeObjFlags = 2u32.pow(i).into();
|
let flag: CodeObjFlags = 2u32.pow(i).into();
|
||||||
write!(flagged, "{:?}, ", flag).unwrap();
|
write!(flagged, "{flag:?}, ").unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flagged.pop();
|
flagged.pop();
|
||||||
flagged.pop();
|
flagged.pop();
|
||||||
writeln!(attrs, "Flags: {}", flagged).unwrap();
|
writeln!(attrs, "Flags: {flagged}").unwrap();
|
||||||
attrs
|
attrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -452,22 +452,22 @@ impl CodeObj {
|
||||||
let mut instrs = "".to_string();
|
let mut instrs = "".to_string();
|
||||||
writeln!(instrs, "lnotab: {:?}", self.lnotab).unwrap();
|
writeln!(instrs, "lnotab: {:?}", self.lnotab).unwrap();
|
||||||
if *sdelta != 0 {
|
if *sdelta != 0 {
|
||||||
writeln!(instrs, "{}:", lineno).unwrap();
|
writeln!(instrs, "{lineno}:").unwrap();
|
||||||
}
|
}
|
||||||
loop {
|
loop {
|
||||||
if *sdelta as usize == line_offset {
|
if *sdelta as usize == line_offset {
|
||||||
line_offset = 0;
|
line_offset = 0;
|
||||||
lineno += *ldelta as u32;
|
lineno += *ldelta as u32;
|
||||||
writeln!(instrs, "{}:", lineno).unwrap();
|
writeln!(instrs, "{lineno}:").unwrap();
|
||||||
sdelta = lnotab_iter.next().unwrap_or(&0);
|
sdelta = lnotab_iter.next().unwrap_or(&0);
|
||||||
ldelta = lnotab_iter.next().unwrap_or(&0);
|
ldelta = lnotab_iter.next().unwrap_or(&0);
|
||||||
}
|
}
|
||||||
if let (Some(op), Some(arg)) = (code_iter.next(), code_iter.next()) {
|
if let (Some(op), Some(arg)) = (code_iter.next(), code_iter.next()) {
|
||||||
match py_ver.and_then(|pv| pv.minor) {
|
match py_ver.and_then(|pv| pv.minor) {
|
||||||
Some(8) => self.read_instr_3_8(op, arg, idx, &mut instrs),
|
Some(8) => self.read_instr_308(op, arg, idx, &mut instrs),
|
||||||
// Some(9) => self.read_instr_3_9(op, arg, idx, &mut instrs),
|
// Some(9) => self.read_instr_3_9(op, arg, idx, &mut instrs),
|
||||||
Some(10) => self.read_instr_3_10(op, arg, idx, &mut instrs),
|
Some(10) => self.read_instr_310(op, arg, idx, &mut instrs),
|
||||||
Some(11) => self.read_instr_3_11(op, arg, idx, &mut instrs),
|
Some(11) => self.read_instr_311(op, arg, idx, &mut instrs),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
idx += 2;
|
idx += 2;
|
||||||
|
@ -479,26 +479,33 @@ impl CodeObj {
|
||||||
instrs
|
instrs
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_instr_3_8(&self, op: &u8, arg: &u8, idx: usize, instrs: &mut String) {
|
fn read_instr_308(&self, op: &u8, arg: &u8, idx: usize, instrs: &mut String) {
|
||||||
let op38 = Opcode308::from(*op);
|
let op308 = Opcode308::from(*op);
|
||||||
let s_op = op38.to_string();
|
let s_op = op308.to_string();
|
||||||
write!(instrs, "{:>15} {:<25}", idx, s_op).unwrap();
|
write!(instrs, "{:>15} {:<25}", idx, s_op).unwrap();
|
||||||
if let Ok(op) = CommonOpcode::try_from(*op) {
|
if let Ok(op) = CommonOpcode::try_from(*op) {
|
||||||
self.dump_additional_info(op, arg, idx, instrs);
|
self.dump_additional_info(op, arg, idx, instrs);
|
||||||
}
|
}
|
||||||
match op38 {
|
match op308 {
|
||||||
|
Opcode308::JUMP_ABSOLUTE => {
|
||||||
|
write!(instrs, "{arg} (to {})", *arg as usize * 2).unwrap();
|
||||||
|
}
|
||||||
|
// REVIEW: *2?
|
||||||
|
Opcode308::POP_JUMP_IF_FALSE | Opcode308::POP_JUMP_IF_TRUE => {
|
||||||
|
write!(instrs, "{arg} (to {})", *arg as usize * 2).unwrap();
|
||||||
|
}
|
||||||
Opcode308::BINARY_ADD
|
Opcode308::BINARY_ADD
|
||||||
| Opcode308::BINARY_SUBTRACT
|
| Opcode308::BINARY_SUBTRACT
|
||||||
| Opcode308::BINARY_MULTIPLY
|
| Opcode308::BINARY_MULTIPLY
|
||||||
| Opcode308::BINARY_TRUE_DIVIDE => {
|
| Opcode308::BINARY_TRUE_DIVIDE => {
|
||||||
write!(instrs, "{} ({:?})", arg, TypePair::from(*arg)).unwrap();
|
write!(instrs, "{arg} ({:?})", TypePair::from(*arg)).unwrap();
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
instrs.push('\n');
|
instrs.push('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_instr_3_10(&self, op: &u8, arg: &u8, idx: usize, instrs: &mut String) {
|
fn read_instr_310(&self, op: &u8, arg: &u8, idx: usize, instrs: &mut String) {
|
||||||
let op310 = Opcode310::from(*op);
|
let op310 = Opcode310::from(*op);
|
||||||
let s_op = op310.to_string();
|
let s_op = op310.to_string();
|
||||||
write!(instrs, "{:>15} {:<25}", idx, s_op).unwrap();
|
write!(instrs, "{:>15} {:<25}", idx, s_op).unwrap();
|
||||||
|
@ -506,42 +513,55 @@ impl CodeObj {
|
||||||
self.dump_additional_info(op, arg, idx, instrs);
|
self.dump_additional_info(op, arg, idx, instrs);
|
||||||
}
|
}
|
||||||
match op310 {
|
match op310 {
|
||||||
|
Opcode310::JUMP_ABSOLUTE => {
|
||||||
|
write!(instrs, "{arg} (to {})", *arg as usize * 2).unwrap();
|
||||||
|
}
|
||||||
|
Opcode310::POP_JUMP_IF_FALSE | Opcode310::POP_JUMP_IF_TRUE => {
|
||||||
|
write!(instrs, "{arg} (to {})", *arg as usize * 2).unwrap();
|
||||||
|
}
|
||||||
Opcode310::BINARY_ADD
|
Opcode310::BINARY_ADD
|
||||||
| Opcode310::BINARY_SUBTRACT
|
| Opcode310::BINARY_SUBTRACT
|
||||||
| Opcode310::BINARY_MULTIPLY
|
| Opcode310::BINARY_MULTIPLY
|
||||||
| Opcode310::BINARY_TRUE_DIVIDE => {
|
| Opcode310::BINARY_TRUE_DIVIDE => {
|
||||||
write!(instrs, "{} ({:?})", arg, TypePair::from(*arg)).unwrap();
|
write!(instrs, "{arg} ({:?})", TypePair::from(*arg)).unwrap();
|
||||||
}
|
}
|
||||||
Opcode310::SETUP_WITH => {
|
Opcode310::SETUP_WITH => {
|
||||||
write!(instrs, "{} (to {})", arg, idx + *arg as usize * 2 + 2).unwrap();
|
write!(instrs, "{arg} (to {})", idx + *arg as usize * 2 + 2).unwrap();
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
instrs.push('\n');
|
instrs.push('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_instr_3_11(&self, op: &u8, arg: &u8, idx: usize, instrs: &mut String) {
|
fn read_instr_311(&self, op: &u8, arg: &u8, idx: usize, instrs: &mut String) {
|
||||||
let op311 = Opcode311::from(*op);
|
let op311 = Opcode311::from(*op);
|
||||||
let s_op = op311.to_string();
|
let s_op = op311.to_string();
|
||||||
write!(instrs, "{:>15} {:<25}", idx, s_op).unwrap();
|
write!(instrs, "{idx:>15} {s_op:<26}").unwrap();
|
||||||
if let Ok(op) = CommonOpcode::try_from(*op) {
|
if let Ok(op) = CommonOpcode::try_from(*op) {
|
||||||
self.dump_additional_info(op, arg, idx, instrs);
|
self.dump_additional_info(op, arg, idx, instrs);
|
||||||
}
|
}
|
||||||
match op311 {
|
match op311 {
|
||||||
Opcode311::PRECALL | Opcode311::CALL => {
|
Opcode311::POP_JUMP_FORWARD_IF_FALSE
|
||||||
write!(instrs, "{}", arg).unwrap();
|
| Opcode311::POP_JUMP_FORWARD_IF_TRUE => {
|
||||||
|
write!(instrs, "{arg} (to {})", idx + *arg as usize * 2 + 2).unwrap();
|
||||||
|
}
|
||||||
|
Opcode311::JUMP_BACKWARD => {
|
||||||
|
write!(instrs, "{arg} (to {})", idx - *arg as usize * 2 + 2).unwrap();
|
||||||
|
}
|
||||||
|
Opcode311::PRECALL | Opcode311::CALL
|
||||||
|
| Opcode311::COPY | Opcode311::SWAP => {
|
||||||
|
write!(instrs, "{arg}").unwrap();
|
||||||
}
|
}
|
||||||
Opcode311::KW_NAMES => {
|
Opcode311::KW_NAMES => {
|
||||||
write!(
|
write!(
|
||||||
instrs,
|
instrs,
|
||||||
"{} ({})",
|
"{arg} ({})",
|
||||||
arg,
|
|
||||||
self.consts.get(*arg as usize).unwrap()
|
self.consts.get(*arg as usize).unwrap()
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
Opcode311::BINARY_OP => {
|
Opcode311::BINARY_OP => {
|
||||||
write!(instrs, "{} ({:?})", arg, BinOpCode::from(*arg)).unwrap();
|
write!(instrs, "{arg} ({:?})", BinOpCode::from(*arg)).unwrap();
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
@ -560,7 +580,7 @@ impl CodeObj {
|
||||||
5 => ">=",
|
5 => ">=",
|
||||||
_ => "?",
|
_ => "?",
|
||||||
};
|
};
|
||||||
write!(instrs, "{} ({})", arg, op).unwrap();
|
write!(instrs, "{arg} ({op})").unwrap();
|
||||||
}
|
}
|
||||||
CommonOpcode::STORE_NAME
|
CommonOpcode::STORE_NAME
|
||||||
| CommonOpcode::LOAD_NAME
|
| CommonOpcode::LOAD_NAME
|
||||||
|
@ -573,8 +593,7 @@ impl CodeObj {
|
||||||
| CommonOpcode::IMPORT_FROM => {
|
| CommonOpcode::IMPORT_FROM => {
|
||||||
write!(
|
write!(
|
||||||
instrs,
|
instrs,
|
||||||
"{} ({})",
|
"{arg} ({})",
|
||||||
arg,
|
|
||||||
self.names.get(*arg as usize).unwrap()
|
self.names.get(*arg as usize).unwrap()
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -582,8 +601,7 @@ impl CodeObj {
|
||||||
CommonOpcode::STORE_DEREF | CommonOpcode::LOAD_DEREF => {
|
CommonOpcode::STORE_DEREF | CommonOpcode::LOAD_DEREF => {
|
||||||
write!(
|
write!(
|
||||||
instrs,
|
instrs,
|
||||||
"{} ({})",
|
"{arg} ({})",
|
||||||
arg,
|
|
||||||
self.freevars.get(*arg as usize).unwrap()
|
self.freevars.get(*arg as usize).unwrap()
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -591,8 +609,7 @@ impl CodeObj {
|
||||||
CommonOpcode::LOAD_CLOSURE => {
|
CommonOpcode::LOAD_CLOSURE => {
|
||||||
write!(
|
write!(
|
||||||
instrs,
|
instrs,
|
||||||
"{} ({})",
|
"{arg} ({})",
|
||||||
arg,
|
|
||||||
self.cellvars.get(*arg as usize).unwrap()
|
self.cellvars.get(*arg as usize).unwrap()
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -600,8 +617,7 @@ impl CodeObj {
|
||||||
CommonOpcode::STORE_FAST | CommonOpcode::LOAD_FAST => {
|
CommonOpcode::STORE_FAST | CommonOpcode::LOAD_FAST => {
|
||||||
write!(
|
write!(
|
||||||
instrs,
|
instrs,
|
||||||
"{} ({})",
|
"{arg} ({})",
|
||||||
arg,
|
|
||||||
self.varnames.get(*arg as usize).unwrap()
|
self.varnames.get(*arg as usize).unwrap()
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -609,23 +625,16 @@ impl CodeObj {
|
||||||
CommonOpcode::LOAD_CONST => {
|
CommonOpcode::LOAD_CONST => {
|
||||||
write!(
|
write!(
|
||||||
instrs,
|
instrs,
|
||||||
"{} ({})",
|
"{arg} ({})",
|
||||||
arg,
|
|
||||||
self.consts.get(*arg as usize).unwrap()
|
self.consts.get(*arg as usize).unwrap()
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
CommonOpcode::FOR_ITER => {
|
CommonOpcode::FOR_ITER => {
|
||||||
write!(instrs, "{} (to {})", arg, idx + *arg as usize * 2 + 2).unwrap();
|
write!(instrs, "{arg} (to {})", idx + *arg as usize * 2 + 2).unwrap();
|
||||||
}
|
}
|
||||||
CommonOpcode::JUMP_FORWARD => {
|
CommonOpcode::JUMP_FORWARD => {
|
||||||
write!(instrs, "{} (to {})", arg, idx + *arg as usize * 2 + 2).unwrap();
|
write!(instrs, "{arg} (to {})", idx + *arg as usize * 2 + 2).unwrap();
|
||||||
}
|
|
||||||
CommonOpcode::JUMP_ABSOLUTE => {
|
|
||||||
write!(instrs, "{} (to {})", arg, *arg as usize * 2).unwrap();
|
|
||||||
}
|
|
||||||
CommonOpcode::POP_JUMP_IF_FALSE | CommonOpcode::POP_JUMP_IF_TRUE => {
|
|
||||||
write!(instrs, "{} (to {})", arg, *arg as usize * 2).unwrap();
|
|
||||||
}
|
}
|
||||||
CommonOpcode::MAKE_FUNCTION => {
|
CommonOpcode::MAKE_FUNCTION => {
|
||||||
let flag = match arg {
|
let flag = match arg {
|
||||||
|
@ -633,10 +642,10 @@ impl CodeObj {
|
||||||
// TODO:
|
// TODO:
|
||||||
_ => "",
|
_ => "",
|
||||||
};
|
};
|
||||||
write!(instrs, "{} {}", arg, flag).unwrap();
|
write!(instrs, "{arg} {flag}").unwrap();
|
||||||
}
|
}
|
||||||
other if other.take_arg() => {
|
other if other.take_arg() => {
|
||||||
write!(instrs, "{}", arg).unwrap();
|
write!(instrs, "{arg}").unwrap();
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
@ -644,7 +653,7 @@ impl CodeObj {
|
||||||
|
|
||||||
pub fn code_info(&self, py_ver: Option<PythonVersion>) -> String {
|
pub fn code_info(&self, py_ver: Option<PythonVersion>) -> String {
|
||||||
let mut info = "".to_string();
|
let mut info = "".to_string();
|
||||||
writeln!(info, "Disassembly of {:?}:", self).unwrap();
|
writeln!(info, "Disassembly of {self:?}:").unwrap();
|
||||||
info += &self.attrs_info();
|
info += &self.attrs_info();
|
||||||
info += &self.tables_info();
|
info += &self.tables_info();
|
||||||
info += &self.instr_info(py_ver);
|
info += &self.instr_info(py_ver);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue