Bangun interpreter di Java - Implementasikan mesin eksekusi

Sebelumnya 1 2 3 Halaman 2 Berikutnya Halaman 2 dari 3

Aspek lain: String dan array

Dua bagian lain dari bahasa BASIC diimplementasikan oleh penerjemah COCOA: string dan array. Mari kita lihat implementasi string terlebih dahulu.

Untuk mengimplementasikan string sebagai variabel, Expressionkelas telah dimodifikasi untuk menyertakan pengertian ekspresi "string". Modifikasi ini berupa dua penambahan: isStringdan stringValue. Sumber untuk kedua metode baru ini ditampilkan di bawah.

String value value (Program pgm) melempar BASICRuntimeError {throw new BASICRuntimeError ("Tidak ada representasi String untuk ini."); } boolean isString () {return false; }

Jelas, itu tidak terlalu berguna untuk program BASIC untuk mendapatkan nilai string dari ekspresi dasar (yang selalu berupa ekspresi numerik atau boolean). Anda mungkin menyimpulkan dari kurangnya utilitas bahwa metode-metode ini kemudian tidak termasuk Expressiondan Expressionmalah termasuk dalam subkelas . Namun, dengan meletakkan kedua metode ini di kelas dasar, semua Expressionobjek dapat diuji untuk melihat apakah sebenarnya mereka adalah string.

Pendekatan desain lainnya adalah mengembalikan nilai numerik sebagai string menggunakan StringBufferobjek untuk menghasilkan nilai. Jadi, misalnya, kode yang sama dapat ditulis ulang sebagai:

String nilai string (Program pgm) melempar BASICRuntimeError {StringBuffer sb = new StringBuffer (); sb.append (this.value (pgm)); return sb.toString (); }

Dan jika kode di atas digunakan, Anda dapat menghilangkan penggunaan isStringkarena setiap ekspresi dapat mengembalikan nilai string. Lebih lanjut, Anda dapat memodifikasi valuemetode untuk mencoba mengembalikan angka jika ekspresi mengevaluasi ke string dengan menjalankannya melalui valueOfmetode java.lang.Double. Dalam banyak bahasa seperti Perl, TCL, dan REXX, jenis pengetikan amorf ini digunakan untuk keuntungan besar. Kedua pendekatan tersebut valid, dan Anda harus membuat pilihan berdasarkan desain penerjemah Anda. Dalam BASIC, penerjemah perlu mengembalikan kesalahan ketika string diberikan ke variabel numerik, jadi saya memilih pendekatan pertama (mengembalikan kesalahan).

Untuk array, ada berbagai cara untuk mendesain bahasa Anda untuk menafsirkannya. C menggunakan tanda kurung siku di sekitar elemen array untuk membedakan referensi indeks array dari referensi fungsi yang memiliki tanda kurung di sekitar argumennya. Namun, desainer bahasa untuk BASIC memilih untuk menggunakan tanda kurung untuk kedua fungsi dan array sehingga ketika teks NAME(V1, V2)dilihat oleh parser, itu bisa berupa panggilan fungsi atau referensi array.

Penganalisis leksikal membedakan antara token yang diikuti oleh tanda kurung dengan terlebih dahulu mengasumsikan bahwa itu adalah fungsi dan mengujinya. Kemudian berlanjut untuk melihat apakah itu kata kunci atau variabel. Keputusan inilah yang mencegah program Anda mendefinisikan variabel bernama "SIN". Variabel apa pun yang namanya cocok dengan nama fungsi akan dikembalikan oleh penganalisis leksikal sebagai token fungsi. Trik kedua yang digunakan penganalisis leksikal adalah untuk memeriksa apakah nama variabel segera diikuti oleh '('. Jika ya, penganalisis menganggapnya sebagai referensi larik. Dengan menguraikannya di penganalisis leksikal, kami menghilangkan string ` MYARRAY ( 2 )'agar tidak ditafsirkan sebagai larik yang valid (perhatikan spasi antara nama variabel dan tanda kurung buka).

Trik terakhir untuk mengimplementasikan array ada di dalam Variablekelas. Kelas ini digunakan untuk instance variabel, dan seperti yang saya bahas di kolom bulan lalu, ini adalah subkelas dari Token. Namun, ini juga memiliki beberapa mesin untuk mendukung array dan itulah yang akan saya tunjukkan di bawah ini:

class Variable extends Token {// Variabel hukum sub jenis final static int NUMBER = 0; akhir statis int STRING = 1; static int terakhir NUMBER_ARRAY = 2; akhir statis int STRING_ARRAY = 4; Nama string; int subType; / * * Jika variabel ada dalam tabel simbol, nilai-nilai ini * diinisialisasi. * / int ndx []; // indeks larik. int mult []; // pengali array ganda nArrayValues ​​[]; String sArrayValues ​​[];

Kode di atas menunjukkan variabel instan yang terkait dengan variabel, seperti di ConstantExpressionkelas. Seseorang harus membuat pilihan tentang jumlah kelas yang akan digunakan versus kompleksitas kelas. Salah satu pilihan desain mungkin adalah membangun Variablekelas yang hanya menampung variabel skalar dan kemudian menambahkan ArrayVariablesubkelas untuk menangani seluk-beluk array. Saya memilih untuk menggabungkannya, mengubah variabel skalar pada dasarnya menjadi array dengan panjang 1.

Jika Anda membaca kode di atas, Anda akan melihat indeks dan pengganda array. Ini ada di sini karena array multidimensi di BASIC diimplementasikan menggunakan array Java linier tunggal. Indeks linier ke dalam larik Java dihitung secara manual menggunakan elemen dari larik pengali. Indeks yang digunakan dalam program BASIC diperiksa validitasnya dengan membandingkannya dengan indeks legal maksimum dalam larik ndx indeks .

Misalnya, array BASIC dengan tiga dimensi 10, 10, dan 8, akan memiliki nilai 10, 10, dan 8 yang disimpan dalam ndx. Hal ini memungkinkan pengevaluasi ekspresi untuk menguji kondisi "indeks di luar batas" dengan membandingkan nomor yang digunakan dalam program BASIC dengan nomor resmi maksimum yang sekarang disimpan di ndx. Array pengali dalam contoh kita akan berisi nilai 1, 10, dan 100. Konstanta ini mewakili angka yang digunakan untuk memetakan dari spesifikasi indeks larik multidimensi ke dalam spesifikasi indeks larik linier. Persamaan sebenarnya adalah:

Indeks Java = Indeks1 + Indeks2 * Ukuran Maksimal Indeks1 + Indeks3 * (Ukuran Maksimal Indeks1 * MaxSizeIndex 2)

Array Java berikutnya di Variablekelas ditampilkan di bawah ini.

 Ekspresi expns []; 

The expns array digunakan untuk menangani array yang ditulis sebagai A(10*B, i)"" Dalam kasus tersebut, indeks sebenarnya adalah ekspresi daripada konstanta, jadi referensi harus berisi penunjuk ke ekspresi tersebut yang dievaluasi pada waktu proses. Terakhir, ada bagian kode yang tampak cukup jelek yang menghitung indeks tergantung pada apa yang diteruskan dalam program. Metode pribadi ini ditunjukkan di bawah.

private int computeIndex (int ii []) menampilkan BASICRuntimeError {int offset = 0; if ((ndx == null) || (ii.length! = ndx.length)) lemparkan BASICRuntimeError baru ("Jumlah indeks salah."); untuk (int i = 0; i <ndx.length; i ++) {if ((ii [i] ndx [i])) lemparkan BASICRuntimeError baru ("Indeks di luar rentang."); offset = offset + (ii [i] -1) * mult [i]; } pengembalian offset; }

Melihat kode di atas, Anda akan melihat bahwa kode pertama kali memeriksa untuk melihat bahwa jumlah indeks yang benar digunakan saat mereferensikan larik, dan kemudian setiap indeks berada dalam kisaran legal untuk indeks itu. Jika kesalahan terdeteksi, pengecualian dilemparkan ke interpreter. Metode numValuedan stringValuemengembalikan nilai dari variabel sebagai angka atau string masing-masing. Kedua metode tersebut ditunjukkan di bawah ini.

double numValue (int ii []) melempar BASICRuntimeError {return nArrayValues ​​[computeIndex (ii)]; } String value value (int ii []) melempar BASICRuntimeError {if (subType == NUMBER_ARRAY) return "" + nArrayValues ​​[computeIndex (ii)]; return sArrayValues ​​[computeIndex (ii)]; }

Ada metode tambahan untuk menyetel nilai variabel yang tidak ditampilkan di sini.

Dengan menyembunyikan banyak kerumitan bagaimana masing-masing bagian diimplementasikan, ketika akhirnya tiba saatnya untuk menjalankan program BASIC, kode Java cukup mudah.

Menjalankan kode

Kode untuk menafsirkan pernyataan BASIC dan mengeksekusinya ada di file

run

metode dari

Program

kelas. Kode untuk metode ini ditunjukkan di bawah ini, dan saya akan menjelaskannya untuk menunjukkan bagian yang menarik.

1 public void run (InputStream masuk, OutputStream out) melempar BASICRuntimeError {2 PrintStream cemberut; 3 Pencacahan e = stmts.elements (); 4 stmtStack = Stack baru (); // asumsikan tidak ada pernyataan bertumpuk ... 5 dataStore = new Vector (); // ... dan tidak ada data untuk dibaca. 6 dataPtr = 0; 7 Pernyataan s; 8 9 vars = baru RedBlackTree (); 10 11 // jika program belum valid. 12 if (! E.hasMoreElements ()) 13 kembali; 14 15 jika (keluar dari PrintStream) {16 pout = (PrintStream) keluar; 17} else {18 pout = new PrintStream (keluar); 19}

Kode di atas menunjukkan bahwa runmetode mengambil InputStreamdan OutputStreamuntuk digunakan sebagai "konsol" untuk program yang sedang dijalankan. Di baris 3, objek enumerasi e diatur ke himpunan pernyataan dari koleksi bernama stmts . Untuk koleksi ini saya menggunakan variasi pada pohon pencarian biner yang disebut pohon "merah-hitam". (Untuk informasi lebih lanjut tentang pohon pencarian biner, lihat kolom saya sebelumnya tentang membangun koleksi generik.) Setelah itu, dua koleksi tambahan dibuat - satu menggunakan a Stackdan satu lagi menggunakan aVector. Tumpukan digunakan seperti tumpukan di komputer mana pun, tetapi vektor digunakan secara tegas untuk pernyataan DATA dalam program BASIC. Koleksi terakhir adalah pohon merah-hitam lain yang menyimpan referensi untuk variabel yang ditentukan oleh program BASIC. Pohon ini adalah tabel simbol yang digunakan oleh program saat sedang dijalankan.

Setelah inisialisasi, aliran input dan output diatur, dan kemudian jika e bukan null, kita mulai dengan mengumpulkan data apa pun yang telah dideklarasikan. Itu dilakukan seperti yang ditunjukkan pada kode berikut.

/ * Pertama kita memuat semua pernyataan data * / while (e.hasMoreElements ()) {s = (Statement) e.nextElement (); if (s.keyword == Statement.DATA) {s.execute (this, in, pout); }}

Loop di atas hanya melihat semua pernyataan, dan pernyataan DATA apa pun yang ditemukannya kemudian dieksekusi. Eksekusi setiap pernyataan DATA memasukkan nilai yang dideklarasikan oleh pernyataan tersebut ke dalam vektor dataStore . Selanjutnya kami menjalankan program dengan benar, yang dilakukan menggunakan potongan kode berikut ini:

e = stmts.elements (); s = (Pernyataan) e.nextElement (); lakukan {int yyy; / * Saat menjalankan kita melewatkan pernyataan Data. * / coba {yyy = in.available (); } menangkap (IOException ez) {yyy = 0; } if (yyy! = 0) {pout.println ("Berhenti di:" + s); dorong; istirahat; } if (s.keyword! = Statement.DATA) {if (traceState) {s.trace (ini, (traceFile! = null)? traceFile: pout); } s = s.execute (this, in, pout); } lain s = nextStatement (s); } sementara (s! = null); }

Seperti yang Anda lihat pada kode di atas, langkah pertama adalah menginisialisasi ulang e . Langkah selanjutnya adalah mengambil pernyataan pertama ke dalam variabel s dan kemudian memasuki loop eksekusi. Ada beberapa kode untuk memeriksa input tertunda pada aliran input untuk memungkinkan kemajuan program terganggu dengan mengetik di program, dan kemudian loop memeriksa untuk melihat apakah pernyataan yang akan dieksekusi adalah pernyataan DATA. Jika ya, loop melewatkan pernyataan seperti itu sudah dieksekusi. Teknik yang agak berbelit-belit untuk mengeksekusi semua pernyataan data terlebih dahulu diperlukan karena BASIC memungkinkan pernyataan DATA yang memenuhi pernyataan BACA muncul di mana saja dalam kode sumber. Akhirnya, jika pelacakan diaktifkan, rekaman jejak dicetak dan pernyataan yang sangat tidak mengesankans = s.execute(this, in, pout);dipanggil. Keindahannya adalah bahwa semua upaya merangkum konsep dasar ke dalam kelas yang mudah dipahami membuat kode akhir menjadi sepele. Jika tidak sepele maka mungkin Anda memiliki petunjuk bahwa mungkin ada cara lain untuk membagi desain Anda.

Membungkus dan pemikiran lebih lanjut

Penerjemah dirancang agar dapat berjalan sebagai utas, sehingga dapat ada beberapa utas penerjemah COCOA yang berjalan secara bersamaan di ruang program Anda pada saat yang bersamaan. Lebih lanjut, dengan penggunaan perluasan fungsi, kami dapat menyediakan sarana agar utas tersebut dapat berinteraksi satu sama lain. Ada program untuk Apple II dan kemudian untuk PC dan Unix yang disebut C-robot yang merupakan sistem interaksi entitas "robotik" yang diprogram menggunakan bahasa turunan BASIC sederhana. Permainan ini memberi saya dan orang lain hiburan berjam-jam, tetapi juga merupakan cara terbaik untuk memperkenalkan prinsip dasar komputasi kepada siswa yang lebih muda (yang secara keliru percaya bahwa mereka hanya bermain dan tidak belajar).Subsistem penerjemah berbasis Java jauh lebih kuat daripada rekan pra-Java mereka karena mereka langsung tersedia di platform Java apa pun. COCOA berjalan pada sistem Unix dan Macintosh pada hari yang sama ketika saya bekerja pada PC berbasis Windows 95. Meskipun Java dihancurkan oleh ketidaksesuaian dalam implementasi toolkit thread atau jendela, yang sering diabaikan adalah ini: Banyak kode "langsung berfungsi".