כיצד לבנות מתורגמן בג'אווה, חלק 1: הבסיסים

כשאמרתי לחבר שכתבתי מתורגמן BASIC בג'אווה, הוא צחק כל כך חזק שהוא כמעט שפך את הסודה שהחזיק על כל בגדיו. "למה בכלל היית בונה מתורגמן BASIC בג'אווה?" הייתה השאלה הראשונה הצפויה מפיו. התשובה פשוטה ומורכבת כאחד. התשובה הפשוטה היא שכיף היה לכתוב מתורגמן בג'אווה, ואם אני אכתוב מתורגמן, אני יכול לכתוב גם אחד שיש לי זיכרונות נעימים מהימים הראשונים של המחשוב האישי. בצד המורכב, שמתי לב שאנשים רבים המשתמשים בג'אווה כיום עברו את הנקודה ליצור יישומונים של דיוק נופלים ועוברים ליישומים רציניים. לעתים קרובות, בבניית יישום, היית רוצה שניתן יהיה להגדיר אותו.מנגנון הבחירה לתצורה מחדש הוא סוג של מנוע ביצוע דינמי.

המכונה שפות מאקרו, או שפות תצורה, ביצוע דינמי הוא התכונה המאפשרת "לתכנת" יישום על ידי המשתמש. היתרון שיש במנוע ביצוע דינמי בכך שניתן להתאים כלים ויישומים לביצוע משימות מורכבות מבלי להחליף את הכלי. פלטפורמת Java מציעה מגוון רחב של אפשרויות מנוע ביצוע דינמי.

HotJava ואפשרויות חמות אחרות

בואו נחקור בקצרה כמה אפשרויות מנוע הביצוע הדינמי הקיים ואז נבחן את יישום המתורגמן שלי לעומק. מנוע ביצוע דינמי הוא מתורגמן משובץ. מתורגמן זקוק לשלושה מתקנים על מנת שיפעל:

  1. אמצעי להטעין הוראות
  2. פורמט מודול לאחסון הוראות לביצוע
  3. מודל או סביבה לאינטראקציה עם התוכנית המארחת

HotJava

המתורגמן המשובץ המפורסם ביותר צריך להיות סביבת "היישומון" של HotJava שעיצבה מחדש את האופן שבו אנשים מסתכלים על דפדפני האינטרנט.

מודל "היישומון" של HotJava התבסס על התפיסה כי יישום Java יכול ליצור מחלקת בסיס כללית עם ממשק ידוע, ואז לטעון באופן דינמי מחלקות משנה של אותה מחלקה ולבצע אותן בזמן הריצה. יישומונים אלה סיפקו יכולות חדשות, ובתחום מחלקת הבסיס סיפקו ביצוע דינמי. יכולת ביצוע דינמית זו היא חלק מהותי בסביבת ג'אווה ואחד הדברים שהופכים אותה למיוחדת כל כך. אנו נסתכל על הסביבה הספציפית הזו לעומק בטור מאוחר יותר.

GNU EMACS

לפני הגעת HotJava, היישום המוצלח ביותר אולי עם ביצוע דינמי היה GNU EMACS. שפת המאקרו הדומה ל- LISP של עורך זה הפכה למרכיב מרכזי עבור מתכנתים רבים. בקצרה, סביבת ה- EMACS LISP מורכבת מתורגמן LISP ופונקציות רבות מסוג עריכה בהן ניתן להשתמש כדי להרכיב את המאקרו המורכבים ביותר. אין לראות במפתיע שעורך EMACS במקור נכתב במקרו המיועד לעורך בשם TECO. לפיכך, הזמינות של שפת מאקרו עשירה (אם לא ניתנת לקריאה) ב- TECO אפשרה לבנות עורך חדש לחלוטין. כיום, GNU EMACS הוא עורך הבסיס, ומשחקים שלמים נכתבו בלא יותר מאשר קוד ה- EMACS LISP, המכונה el-code. יכולת תצורה זו הפכה את GNU EMACS לעורך עמוד תווך,בעוד שמסופי VT-100 שתוכננו לפעול עליהם הפכו להערות שוליים בלבד בטור הסופר.

REXX

אחת השפות האהובות עלי, שמעולם לא ממש עשתה את הסנסציה הראויה לה, הייתה REXX, שתוכנן על ידי מייק קאוולישאו מ- IBM. החברה הייתה זקוקה לשפה כדי לשלוט ביישומים במסגרות ראשיות גדולות המריצות את מערכת ההפעלה VM. גיליתי את REXX באמיגה, שם היא הותאמה היטב עם מגוון רחב של יישומים דרך "יציאות REXX". יציאות אלה אפשרו להפעיל יישומים מרחוק באמצעות מתורגמן REXX. צימוד זה של מתורגמן ויישום יצר מערכת עוצמתית בהרבה ממה שהיה אפשרי עם חלקיה המרכיבים. למרבה המזל, השפה חיה ב- NETREXX, גרסה שכתב מייק שחוברה לקוד ג'אווה.

כאשר התבוננתי ב- NETREXX ובשפה מוקדמת בהרבה (LISP ב- Java), זה הדהים אותי שהשפות הללו מהוות חלקים חשובים בסיפור יישומי Java. איזו דרך טובה יותר לספר את החלק הזה בסיפור מאשר לעשות כאן משהו מהנה - כמו להחיות את BASIC-80? חשוב מכך, יהיה שימושי להראות דרך אחת שבה ניתן לכתוב שפות סקריפטים ב- Java, ובאמצעות שילובם עם Java, להראות כיצד הם יכולים לשפר את היכולות של יישומי Java שלך.

דרישות בסיסיות לשיפור אפליקציות Java שלך

BASIC היא, בפשטות, שפה בסיסית. ישנן שתי אסכולות כיצד ניתן לכתוב על כך מתורגמן. גישה אחת היא לכתוב לולאת תכנות שבה תוכנית המתורגמנים קוראת שורת טקסט אחת מהתוכנית המתפרשת, מנתחת אותה ואז קוראת לתוכנית משנה לבצע אותה. רצף הקריאה, הניתוח והביצוע חוזר על עצמו עד שאחת מהצהרות התוכנית המתפרשת אומרת למתורגמן להפסיק.

הדרך השנייה והמעניינת הרבה יותר להתמודד עם הפרויקט היא למעשה לנתח את השפה לעץ פרסה ואז לבצע את עץ הפרסה "במקום". כך פועלים מתורגמנים אסימוניים והדרך בה בחרתי להמשיך. מתורגמנים לטקסטים מהירים יותר מכיוון שהם לא צריכים לסרוק מחדש את הקלט בכל פעם שהם מבצעים הצהרה.

כפי שציינתי לעיל, שלושת המרכיבים הדרושים להשגת ביצוע דינמי הם אמצעי טעינה, פורמט מודול וסביבת הביצוע.

הרכיב הראשון, אמצעי טעינה, יטופל על ידי ג'אווה InputStream. מכיוון שזרמי קלט הם בסיסיים בארכיטקטורת ה- I / O של ג'אווה, המערכת נועדה לקרוא בתוכנית מ- InputStreamולהמיר אותה לצורת הפעלה. זה מהווה דרך גמישה מאוד להזנת קוד למערכת. כמובן, הפרוטוקול עבור הנתונים העוברים על זרם הקלט יהיה קוד המקור BASIC. חשוב לציין כי ניתן להשתמש בכל שפה; אל תטעו וחשבו שאי אפשר להחיל את הטכניקה הזו על היישום שלכם.

לאחר הזנת קוד המקור של התוכנית המתפרשת למערכת, המערכת ממירה את קוד המקור לייצוג פנימי. בחרתי להשתמש בעץ המנתח כפורמט הייצוג הפנימי של פרויקט זה. לאחר יצירת עץ הניתוח, ניתן לתפעל אותו או לבצע אותו.

המרכיב השלישי הוא סביבת הביצוע. כפי שנראה, הדרישות לרכיב זה פשוטות למדי, אך ליישום יש כמה פיתולים מעניינים.

סיור BASIC מהיר מאוד

לאלו מכם שמעולם לא שמעו על BASIC, אתן לכם הצצה קצרה לשפה, כדי שתוכלו להבין את אתגרי הניתוח והביצוע העומדים לפניכם. למידע נוסף על BASIC, אני ממליץ בחום על המשאבים בסוף טור זה.

BASIC מייצג קוד הוראה סימבולי למתחילים לכל מטרה, והוא פותח באוניברסיטת דרטמות 'כדי ללמד מושגי חישוב לסטודנטים לתואר ראשון. מאז התפתחותה התפתח BASIC למגוון ניבים. הפשוטים מבין הניבים הללו משמשים כשפות בקרה לבקרי תהליכים תעשייתיים; הניבים המורכבים ביותר הם שפות מובנות המשלבות היבטים מסוימים של תכנות מונחה עצמים. לפרויקט שלי בחרתי בדיאלקט המכונה BASIC-80 שהיה פופולרי במערכת ההפעלה CP / M בסוף שנות השבעים. ניב זה מורכב רק באופן מתון יותר מהניבים הפשוטים ביותר.

תחביר הצהרה

כל שורות ההצהרה הן מהצורה

[: [: ...]]

כאשר "קו" הוא מספר שורת הצהרה, "מילת מפתח" היא מילת מפתח של משפט משפט בסיסי, ו"פרמטרים "הם קבוצת פרמטרים המשויכים למילת מפתח זו.

למספר השורה שתי מטרות: הוא משמש תווית להצהרות השולטות בזרימת הביצוע, כגון gotoמשפט, והוא משמש כתווית מיון להצהרות המוכנסות לתוכנית. כתג מיון, מספר השורה מאפשר סביבת עריכת שורות בה מעורבים עריכה ועיבוד פקודות בפגישה אינטראקטיבית אחת. אגב, זה נדרש כאשר כל מה שהיה לך היה טלטייפ. :-)

אומנם לא מאוד אלגנטי, אך מספרי שורות נותנים לסביבת המתורגמנים את היכולת לעדכן את התוכנית הצהרה אחת בכל פעם. יכולת זו נובעת מכך שהצהרה היא ישות מנותחת אחת וניתן לקשר אותה במבנה נתונים עם מספרי קווים. ללא מספרי שורה, לעתים קרובות יש צורך לנתח מחדש את התוכנית כולה כאשר שורה משתנה.

מילת המפתח מזהה את המשפט BASIC. בדוגמא, המתורגמן שלנו יתמוך סט קצת מורחב של מילות מפתח בסיסיות, כוללים goto, gosub, return, print, if, end, data, restore, read, on, rem, for, next, let, input, stop, dim, randomize, tron, ו troff. ברור שלא נעבור על כל אלה במאמר זה, אך יהיה תיעוד מקוון ב- "Java In Depth" של החודש הבא שתוכלו לחקור.

לכל מילת מפתח קבוצה של פרמטרים חוקיים של מילות מפתח שיכולות לעקוב אחריה. לדוגמא, על gotoמילת המפתח להיות מלווה מספר שורה, על ifההצהרה לאחריה ביטוי מותנה וכן את מילת המפתח then- וכן הלאה. הפרמטרים ספציפיים לכל מילת מפתח. אני אעסוק כמה מרשימות הפרמטרים האלה בפירוט מעט מאוחר יותר.

ביטויים ומפעילים

לעתים קרובות, פרמטר שצוין בהצהרה הוא ביטוי. גרסת ה- BASIC בה אני משתמש כאן תומכת בכל הפעולות המתמטיות הסטנדרטיות, הפעולות הלוגיות, האקספוננציונציה וספריית פונקציות פשוטה. המרכיב החשוב ביותר בדקדוק הביטוי הוא היכולת להתקשר לפונקציות. הביטויים עצמם סטנדרטיים למדי ודומים לאלה שניתחו על ידי הדוגמה בעמודה הקודמת שלי ב- StreamTokenizer.

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:

  • ניתוח לקסיקלי לעיבוד הקוד כטקסט
  • ניתוח ביטוי, לבניית עצי ניתוח של הביטויים
  • ניתוח הצהרה, לבניית עצי ניתוח של ההצהרות עצמן
  • שיעורי שגיאה לדיווח על שגיאות בניתוח

קבוצת המסגרת מורכבת מאובייקטים המחזיקים את עצי הניתוח והמשתנים. אלו כוללים:

  • אובייקט הצהרה עם הרבה מחלקות משנה מיוחדות לייצוג הצהרות מעובדות
  • אובייקט ביטוי לייצוג ביטויים להערכה
  • אובייקט משתנה עם הרבה מחלקות משנה מיוחדות לייצוג מקרים אטומיים של נתונים