Jurubahasa Python saya, Memphis, mempunyai REPL (gelung baca-eval-cetak)!
Ini berita lama. Selagi anda melakukan sifar kesilapan semasa berinteraksi dengan burung hantu tua yang bijak?, anda boleh mentafsir sepuas hati anda. Dengan mengandaikan anda tidak pernah mahu menilai pernyataan yang sama dua kali, atau jika anda melakukannya, tidak keberatan menaipnya semula. Juga dengan sifar kesilapan.
Saya berpuas hati dengan REPL ini. Teruja malah. Saya telah menulis di rumah tentang REPL ini. Tetapi bos-bos bos saya menuntut kami menambah baik REPL untuk kepentingan rakyat. Mereka memanggil saya ke pejabat mereka dan menganggukkan saya ke kerusi di seberang meja mahogani mereka. "Sesetengah pengguna menjangkakan kunci ruang belakang berfungsi." JAHANAM DENGAN PENGGUNA! "Anak panah ke atas harus membawa arahan terakhir mereka." SOKONGAN KUNCI PANAH PANAH BEGITU 9 PULUHAN! "Bolehkah anda melakukan ini pada penghujung Q5?" SAYA BERHENTI!
Jadi saya kembali ke meja saya dan menambah baik REPL.
Saya memperbaiknya sehingga semua kekunci berfungsi. Kekunci ruang belakang, anak panah atas, anak panah ke bawah, anak panah kiri, dan yang terakhir, tetapi tidak kurang pentingnya, anak panah ruang belakang. Seorang akauntan boleh mempunyai hari lapangan dengan REPL baharu. tick tick tick tick tick tick tick tick tick tick. Itulah akauntan yang menaip nombor, bukan bom yang perlahan-lahan meresap.
Saya menghantar REPL ke makmal dan memberitahu jurumesin utama saya untuk membuat kerja tergesa-gesa pada pesanan ini. Ia adalah REPL yang saya katakan dan dari pandangan mata mereka saya dapat tahu mereka faham. 750ms kemudian binaan selesai dan kami mendapat sokongan kekunci anak panah. Saya membawa produk itu kembali ke rambut palsu besar, merayu untuk pekerjaan saya kembali, dan bertanya kepada mereka apa yang mereka fikirkan. Mereka menjalankan beberapa arahan, mencetak beberapa cetakan dan menambah beberapa tambahan. Mereka membuat kesilapan dan menekan kekunci ruang belakang. Saya membulatkan mata kerana serius siapa yang membuat kesilapan tetapi mereka kelihatan berpuas hati. Mereka menyedari bahawa mereka tidak mahu menjalankan perintah panjang yang telah mereka taip dan di sinilah hidup saya pergi ke neraka dalam bakul tangan. mereka. pukul. Ctrl. C. Serius, siapa yang buat begitu?! Anda tahu itu menamatkan proses semasa, bukan? BETUL???
"Kami memerlukan sokongan Ctrl-C menjelang akhir tahun depan." Orang-orang ini dan tuntutan mereka. Saya akan menambah sokongan Ctrl-C. Tetapi ia sama sekali tidak akan berlaku dalam tempoh dua tahun akan datang.
Jadi saya kembali ke meja saya dan menambah sokongan Ctrl-C.
Saya telah mempertaruhkan keseluruhan masa depan profesional dan kewangan saya untuk membina sesuatu "dari awal", jadi saya menghadapi kebingungan pada hari 1 projek ini. Saya memilih untuk menggunakan jangka silang untuk pengesanan kunci terutamanya kerana sokongan merentas platform. Sejujurnya, jangka silang adalah sangat, sangat baik. API adalah intuitif dan saya sangat gembira dengan KeyModifiers (yang kami perlukan untuk mengendalikan Ctrl-C, yang saya fikir tidak perlu, lihat di atas).
Kami memerlukannya supaya terminal tidak mengendalikan kunci khas untuk kami. Tetapi sial, saya tidak menyedari ia akan menukar skrin kita menjadi mesin taip yang tidak berfungsi. Bagaimanapun, saya terpaksa menormalkan semua rentetan untuk menambah pemulangan pengangkutan sebelum sebarang aksara baris baharu. Yang berfungsi dengan baik dan saya TERUJA tentangnya.
/// When the terminal is in raw mode, we must emit a carriage return in addition to a newline, /// because that does not happen automatically. fn normalize<T: Display>(err: T) -> String { let formatted = format!("{}", err); if terminal::is_raw_mode_enabled().expect("Failed to query terminal raw mode") { formatted.replace("\n", "\n\r") } else { formatted.to_string() } } /// Print command which will normalize newlines + carriage returns before printing. fn print_raw<T: Display>(val: T) { print!("{}", normalize(val)); io::stdout().flush().expect("Failed to flush stdout"); }
Juga! Jika anda tidak melumpuhkan mod mentah pada keadaan panik yang tidak dijangka, sesi terminal anda akan menjadi tidak boleh digunakan. Saya memasang pengendali panik tersuai untuk menangkap ini dan bermain dengan baik.
panic::set_hook(Box::new(|info| { // This line is critical!! The rest of this function is just debug info, but without this // line, your shell will become unusable on an unexpected panic. let _ = terminal::disable_raw_mode(); if let Some(s) = info.payload().downcast_ref::<&str>() { eprintln!("\nPanic: {s:?}"); } else if let Some(s) = info.payload().downcast_ref::<String>() { eprintln!("\nPanic: {s:?}"); } else { eprintln!("\nPanic occurred!"); } if let Some(location) = info.location() { eprintln!( " in file '{}' at line {}", location.file(), location.line() ); } else { eprintln!(" in an unknown location."); } process::exit(1); }));
Di bawah REPL lama saya (yang saya suka, lihat di atas), saya boleh menguji integrasinya dengan hanya menjalankan binari dan menghantar beberapa kod Python ke stdin. Itu berhenti berfungsi apabila menggunakan jangka silang saya fikir kerana pertikaian kontrak. Sejujurnya saya tidak dapat menerangkannya sepenuhnya, tetapi event::read() akan tamat masa dan gagal dalam ujian integrasi yang disediakan dengan input stdin. Jadi saya mengejeknya.
pub trait TerminalIO { fn read_event(&mut self) -> Result<Event, io::Error>; fn write<T: Display>(&mut self, output: T) -> io::Result<()>; fn writeln<T: Display>(&mut self, output: T) -> io::Result<()>; } /// A mock for testing that doesn't use `crossterm`. struct MockTerminalIO { /// Predefined events for testing events: Vec<Event>, /// Captured output for assertions output: Vec<String>, } impl TerminalIO for MockTerminalIO { fn read_event(&mut self) -> Result<Event, io::Error> { if self.events.is_empty() { Err(io::Error::new(io::ErrorKind::Other, "No more events")) } else { // remove from the front (semantically similar to VecDequeue::pop_front). Ok(self.events.remove(0)) } } fn write<T: Display>(&mut self, output: T) -> io::Result<()> { self.output.push(format!("{}", output)); Ok(()) } fn writeln<T: Display>(&mut self, output: T) -> io::Result<()> { self.write(output)?; self.write("\n")?; Ok(()) } }
Yang menyebabkan semuanya menjadi ujian unit? Sejujurnya saya tidak tahu. Pada ketika ini, saya memanggilnya ujian integrasi jika saya sama ada a) memanggil binari di dalam binari lain, atau 2) melancarkan pelayan / membuka port / mendengar pada soket di dalam ujian. Jika anda mempunyai definisi lain yang ingin anda tinggalkan dalam ulasan, sila jangan lakukan kerana ia kelihatan menjengkelkan TBH.
Saya mencipta dua fungsi utiliti untuk bermula.
/// Run the complete flow, from input code string to return value string. If you need any Ctrl /// modifiers, do not use this! fn run_and_return(input: &str) -> String { let mut terminal = MockTerminalIO::from_str(input); Repl::new().run(&mut terminal); terminal.return_val() } /// Turn an input string into a list of crossterm events so we don't have to /// hand-compile our test. fn string_to_events(input: &str) -> Vec<Event> { input .chars() .map(|c| { let key_code = match c { '\n' => KeyCode::Enter, _ => KeyCode::Char(c), }; Event::Key(KeyEvent::new(key_code, KeyModifiers::NONE)) }) .collect() }
Dengan menggunakan ini, kami kini boleh menguji senario biasa ini dengan boilerplate yang agak kecil.
#[test] fn test_repl_name_error() { let return_val = run_and_return("e\n"); assert!(return_val.contains("NameError: name 'e' is not defined")); } #[test] fn test_repl_expr() { let third_from_last = run_and_return("12345\n"); assert_eq!(third_from_last, "12345"); } #[test] fn test_repl_statement() { let return_val = run_and_return("a = 5.5\n"); // empty string because a statement does not have a return value assert_eq!(return_val, ""); } #[test] fn test_repl_function() { let code = r#" def foo(): a = 10 return 2 * a foo() "#; let return_val = run_and_return(code); assert_eq!(return_val, "20"); } #[test] fn test_repl_ctrl_c() { let mut events = string_to_events("123456789\n"); let ctrl_c = Event::Key(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL)); events.insert(4, ctrl_c); let mut terminal = MockTerminalIO::new(events); Repl::new().run(&mut terminal); assert_eq!(terminal.return_val(), "56789"); }
Salah satu motivasi saya dalam menambah REPL sama sekali adalah kerana saya percaya anda menjadikan kod anda lebih baik apabila anda menambah titik masuk kedua. Anda pada asasnya menjadi pengguna kedua untuk pustaka anda, yang membantu anda lebih dekat untuk memahami The One Perfect Abstraction yang kami semua mencari papan kekunci kami. Maksud saya perkara ini dengan bersungguh-sungguh.
REPL kini berada di belakang bendera ciri sebagai cara untuk kembali ke pengurusan. Saya mengekalkan keupayaan untuk mentafsir kod Python dengan bantuan peti pihak ketiga sifar, yang bermaksud crossterm sama ada perlu menjadi pengecualian atau saya akan memperkenalkan bendera ciri. Sekarang, jika anda menyusun tanpa REPL didayakan dan menjalankan "memphis", ia akan memberitahu anda dengan sopan "binaan yang salah, bodoh."
REPL ada di sini. Anda boleh menjalankannya seperti ini. Kalau nak beli pun macam tipu je. Semoga sihat & cakap cepat.
Jika anda ingin mendapatkan lebih banyak siaran seperti ini terus ke peti masuk anda, anda boleh melanggan di sini!
Selain membimbing jurutera perisian, saya juga menulis tentang pengalaman saya sebagai orang autistik yang didiagnosis dewasa. Kurang kod dan bilangan jenaka yang sama.
Atas ialah kandungan terperinci REPL untuk Menaip Mesra Jari Lemak. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!