Bagaimana membangun juru bahasa di Jawa, Bagian 1: DASAR-DASAR

Ketika saya memberi tahu seorang teman bahwa saya telah menulis seorang penerjemah BASIC di Jawa, dia tertawa terbahak-bahak hingga hampir menumpahkan soda yang dia pegang ke seluruh pakaiannya. "Mengapa Anda membangun juru bahasa BASIC di Jawa?" adalah pertanyaan pertama yang bisa diprediksi dari mulutnya. Jawabannya sederhana dan rumit. Tanggapan sederhananya adalah bahwa menyenangkan untuk menulis seorang juru bahasa di Java, dan jika saya akan menulis seorang penerjemah, saya mungkin juga menulis satu tentang yang saya memiliki kenangan indah dari hari-hari awal komputasi pribadi. Di sisi yang kompleks, saya memperhatikan bahwa banyak orang yang menggunakan Java saat ini telah melewati titik membuat applet Duke yang jatuh dan beralih ke aplikasi yang serius. Seringkali, dalam membangun aplikasi, Anda ingin agar dapat dikonfigurasi.Mekanisme pilihan untuk konfigurasi ulang adalah semacam mesin eksekusi dinamis.

Dikenal sebagai bahasa makro, atau bahasa konfigurasi, eksekusi dinamis adalah fitur yang memungkinkan aplikasi "diprogram" oleh pengguna. Manfaat memiliki mesin eksekusi dinamis adalah bahwa alat dan aplikasi dapat disesuaikan untuk melakukan tugas yang kompleks tanpa mengganti alat tersebut. Platform Java menawarkan berbagai macam opsi mesin eksekusi dinamis.

HotJava dan opsi panas lainnya

Mari jelajahi secara singkat beberapa opsi mesin eksekusi dinamis yang tersedia dan kemudian lihat implementasi interpreter saya secara mendalam. Mesin eksekusi dinamis adalah interpreter yang disematkan. Seorang juru bahasa membutuhkan tiga fasilitas untuk beroperasi:

  1. Sarana untuk diisi dengan instruksi
  2. Format modul, untuk menyimpan instruksi yang akan dieksekusi
  3. Sebuah model atau lingkungan untuk berinteraksi dengan program host

HotJava

Penerjemah tersemat yang paling terkenal adalah lingkungan "applet" HotJava yang telah sepenuhnya mengubah cara orang melihat browser Web.

Model "applet" HotJava didasarkan pada gagasan bahwa aplikasi Java dapat membuat kelas dasar umum dengan antarmuka yang dikenal, dan kemudian secara dinamis memuat subkelas dari kelas tersebut dan menjalankannya pada waktu proses. Applet ini memberikan kemampuan baru dan, dalam batasan kelas dasar, menyediakan eksekusi dinamis. Kemampuan eksekusi dinamis ini adalah bagian fundamental dari lingkungan Java dan salah satu hal yang membuatnya begitu istimewa. Kami akan melihat lingkungan khusus ini secara mendalam di kolom selanjutnya.

GNU EMACS

Sebelum HotJava hadir, mungkin aplikasi paling sukses dengan eksekusi dinamis adalah GNU EMACS. Bahasa makro seperti LISP editor ini telah menjadi kebutuhan pokok bagi banyak pemrogram. Singkatnya, lingkungan EMACS LISP terdiri dari juru bahasa LISP dan banyak fungsi jenis pengeditan yang dapat digunakan untuk membuat makro yang paling kompleks. Tidaklah mengherankan bahwa editor EMACS awalnya ditulis dalam makro yang dirancang untuk editor yang disebut TECO. Dengan demikian, ketersediaan bahasa makro yang kaya (jika tidak dapat dibaca) di TECO memungkinkan editor yang sama sekali baru dibuat. Saat ini, GNU EMACS adalah editor dasar, dan seluruh game telah ditulis tidak lebih dari kode EMACS LISP, yang dikenal sebagai el-code. Kemampuan konfigurasi ini telah menjadikan GNU EMACS editor andalan,sedangkan terminal VT-100 yang dirancang untuk dijalankan hanya menjadi catatan kaki di kolom penulis.

REXX

Salah satu bahasa favorit saya, yang tidak pernah cukup menarik, adalah REXX, yang dirancang oleh Mike Cowlishaw dari IBM. Perusahaan membutuhkan bahasa untuk mengontrol aplikasi pada mainframe besar yang menjalankan sistem operasi VM. Saya menemukan REXX di Amiga yang dipasangkan dengan erat dengan berbagai macam aplikasi melalui "port REXX". Porta ini memungkinkan aplikasi untuk dijalankan dari jarak jauh melalui interpreter REXX. Penggabungan interpreter dan aplikasi ini menciptakan sistem yang jauh lebih kuat daripada yang mungkin dilakukan dengan bagian-bagian komponennya. Untungnya, bahasa tersebut tetap ada di NETREXX, versi yang ditulis Mike yang dikompilasi ke dalam kode Java.

Ketika saya melihat NETREXX dan bahasa yang jauh lebih awal (LISP di Java), saya tersadar bahwa bahasa-bahasa ini membentuk bagian penting dari cerita aplikasi Java. Cara apa yang lebih baik untuk menceritakan bagian cerita ini selain melakukan sesuatu yang menyenangkan di sini - seperti menghidupkan kembali BASIC-80? Lebih penting lagi, akan berguna untuk menunjukkan satu cara di mana bahasa skrip dapat ditulis di Java dan, melalui integrasinya dengan Java, menunjukkan bagaimana mereka dapat meningkatkan kapabilitas aplikasi Java Anda.

Persyaratan DASAR untuk menyempurnakan aplikasi Java Anda

Sederhananya, BASIC adalah bahasa dasar. Ada dua aliran pemikiran tentang bagaimana seseorang bisa menulis seorang penerjemah untuk itu. Salah satu pendekatannya adalah dengan menulis loop pemrograman di mana program interpreter membaca satu baris teks dari program yang diinterpretasikan, menguraikannya, dan kemudian memanggil subrutin untuk mengeksekusinya. Urutan membaca, parsing, dan mengeksekusi diulangi sampai salah satu pernyataan program yang ditafsirkan memberi tahu penerjemah untuk berhenti.

Cara kedua dan yang jauh lebih menarik untuk menangani proyek sebenarnya adalah dengan mengurai bahasa menjadi pohon parse dan kemudian menjalankan pohon parse "di tempat". Ini adalah cara kerja penafsir tokenizing dan cara saya memilih untuk melanjutkan. Penerjemah tokenizing juga lebih cepat karena mereka tidak perlu memindai ulang input setiap kali mereka menjalankan pernyataan.

Seperti yang saya sebutkan di atas, tiga komponen yang diperlukan untuk mencapai eksekusi dinamis adalah sarana yang dimuat, format modul, dan lingkungan eksekusi.

Komponen pertama, sarana yang dimuat, akan ditangani oleh Java InputStream. Karena aliran input sangat penting dalam arsitektur I / O Java, sistem dirancang untuk membaca program dari InputStreamdan mengubahnya menjadi bentuk yang dapat dieksekusi. Ini merupakan cara yang sangat fleksibel untuk memasukkan kode ke dalam sistem. Tentu saja, protokol untuk data yang melewati aliran input akan menjadi kode sumber BASIC. Penting untuk dicatat bahwa bahasa apa pun dapat digunakan; jangan membuat kesalahan dengan berpikir bahwa teknik ini tidak dapat diterapkan pada aplikasi Anda.

Setelah kode sumber dari program yang diinterpretasikan dimasukkan ke dalam sistem, sistem mengubah kode sumber menjadi representasi internal. Saya memilih untuk menggunakan pohon parse sebagai format representasi internal untuk proyek ini. Setelah pohon parse dibuat, itu dapat dimanipulasi atau dijalankan.

Komponen ketiga adalah lingkungan eksekusi. Seperti yang akan kita lihat, persyaratan untuk komponen ini cukup sederhana, tetapi implementasinya memiliki beberapa perubahan yang menarik.

Tur DASAR yang sangat cepat

Bagi Anda yang mungkin belum pernah mendengar BASIC, saya akan memberi Anda gambaran singkat bahasanya, sehingga Anda dapat memahami tantangan parsing dan eksekusi yang akan datang. Untuk informasi lebih lanjut tentang BASIC, saya sangat merekomendasikan sumber daya di akhir kolom ini.

BASIC adalah singkatan dari Beginners All-purpose Symbolic Instructional Code, dan dikembangkan di Dartmouth University untuk mengajarkan konsep komputasi kepada mahasiswa sarjana. Sejak perkembangannya, BASIC telah berkembang menjadi berbagai dialek. Dialek yang paling sederhana digunakan sebagai bahasa kontrol untuk pengontrol proses industri; dialek paling kompleks adalah bahasa terstruktur yang menggabungkan beberapa aspek pemrograman berorientasi objek. Untuk proyek saya, saya memilih dialek yang dikenal sebagai BASIC-80 yang populer di sistem operasi CP / M di akhir tahun tujuh puluhan. Dialek ini hanya lebih kompleks dari dialek yang paling sederhana.

Sintaks pernyataan

Semua baris pernyataan adalah dalam bentuk

[: [: ...]]

where "Line" is a statement line number, "Keyword" is a BASIC statement keyword, and "Parameters" are a set of parameters associated with that keyword.

The line number has two purposes: It serves as a label for statements that control execution flow, such as a goto statement, and it serves as a sorting tag for statements inserted into the program. As a sorting tag, the line number facilitates a line editing environment in which editing and command processing are mixed in a single interactive session. By the way, this was required when all you had was a teletype. :-)

While not very elegant, line numbers do give the interpreter environment the ability to update the program one statement at a time. This ability stems from the fact that a statement is a single parsed entity and can be linked in a data structure with line numbers. Without line numbers, often it is necessary to re-parse the entire program when a line changes.

The keyword identifies the BASIC statement. In the example, our interpreter will support a slightly extended set of BASIC keywords, including goto, gosub, return, print, if, end, data, restore, read, on, rem, for, next, let, input, stop, dim, randomize, tron, and troff. Obviously, we won't go over all of these in this article, but there will be some documentation online in my next month's "Java In Depth" for you to explore.

Each keyword has a set of legal keyword parameters that can follow it. For example, the goto keyword must be followed by a line number, the if statement must be followed by a conditional expression as well as the keyword then -- and so on. The parameters are specific to each keyword. I'll cover a couple of these parameter lists in detail a bit later.

Expressions and operators

Often, a parameter specified in a statement is an expression. The version of BASIC I'm using here supports all of the standard mathematical operations, logical operations, exponentiation, and a simple function library. The most important component of the expression grammar is the ability to call functions. The expressions themselves are fairly standard and similar to the ones parsed by the example in my previous StreamTokenizer column.

Variables and data types

Part of the reason BASIC is such a simple language is because it has only two data types: numbers and strings. Some scripting languages, such as REXX and PERL, don't even make this distinction between data types until they are used. But with BASIC, a simple syntax is used to identify data types.

Variable names in this version of BASIC are strings of letters and numbers that always start with a letter. Variables are not case-sensitive. Thus A, B, FOO, and FOO2 are all valid variable names. Furthermore, in BASIC, the variable FOOBAR is equivalent to FooBar. To identify strings, a dollar sign ($) is appended to the variable name; thus, the variable FOO$ is a variable containing a string.

Finally, this version of the language supports arrays using the dim keyword and a variable syntax of the form NAME(index1, index2, ...) for up to four indices.

Program structure

Programs in BASIC start by default at the lowest numbered line and continue until there are either no more lines to process or the stop or end keywords are executed. A very simple BASIC program is shown below:

100 REM This is probably the canonical BASIC example 110 REM Program. Note that REM statements are ignored. 120 PRINT "This is a test program." 130 PRINT "Summing the values between 1 and 100" 140 LET total = 0 150 FOR I = 1 TO 100 160 LET total = total + i 170 NEXT I 180 PRINT "The total of all digits between 1 and 100 is " total 190 END 

The line numbers above indicate the lexical order of the statements. When they are run, lines 120 and 130 print messages to the output, line 140 initializes a variable, and the loop in lines 150 through 170 update the value of that variable. Finally, the results are printed out. As you can see, BASIC is a very simple programming language and therefore an ideal candidate for teaching computation concepts.

Organizing the approach

Typical of scripting languages, BASIC involves a program composed of many statements that run in a particular environment. The design challenge, then, is to construct the objects to implement such a system in a useful way.

When I looked at the problem, a straightforward data structure fairly leaped out at me. That structure is as follows:

The public interface to the scripting language shall consist of

  • A factory method that takes source code as input and returns an object representing the program.
  • An environment that provides the framework in which the program executes, including "I/O" devices for text input and text output.
  • A standard way of modifying that object, perhaps in the form of an interface, that allows the program and the environment to be combined to achieve useful results.

Internally, the structure of the interpreter was a bit more complicated. The question was how to go about factoring the two facets of the scripting language, parsing and execution? Three groups of classes resulted -- one for parsing, one for the structural framework of representing parsed and executable programs, and one that formed the base environment class for execution.

In the parsing group, the following objects are required:

  • Analisis leksikal untuk memproses kode sebagai teks
  • Expression parsing, untuk membangun pohon parse dari ekspresi
  • Pernyataan parsing, untuk membuat pohon parse dari pernyataan itu sendiri
  • Kelas kesalahan untuk melaporkan kesalahan dalam penguraian

Grup kerangka kerja terdiri dari objek yang menampung pohon parse dan variabel. Ini termasuk:

  • Objek pernyataan dengan banyak subkelas khusus untuk mewakili pernyataan yang diurai
  • Objek ekspresi untuk mewakili ekspresi untuk evaluasi
  • Objek variabel dengan banyak subclass khusus untuk mewakili contoh data atom