Programování

Jak postavit tlumočníka v Javě, část 1: ZÁKLADY

Když jsem řekl kamarádovi, že jsem psal tlumočníka BASIC v Javě, tak se zasmál, že téměř vysypal sódu, kterou držel, na celé oblečení. „Proč bys na světě stavěl BASIC tlumočníka v Javě?“ byla předvídatelná první otázka z jeho úst. Odpověď je jednoduchá i složitá. Jednoduchá odpověď je, že bylo zábavné psát tlumočníka v Javě, a kdybych měl psát tlumočníka, mohl bych také napsat takový, na který mám příjemné vzpomínky z počátků osobních počítačů. Po složité stránce jsem si všiml, že mnoho lidí, kteří dnes používají Javu, se dostalo přes hranici vytváření padajících Dukeových appletů a přechází k seriózním aplikacím. Při vytváření aplikace byste často chtěli, aby byla konfigurovatelná. Mechanismus volby pro opětovnou konfiguraci je nějaký druh dynamického spouštěcího stroje.

Známý jako jazyky maker nebo konfigurační jazyky, dynamické provádění je funkce, která umožňuje uživateli „naprogramovat“ aplikaci. Výhodou nástroje pro dynamické provádění je, že lze nástroje a aplikace přizpůsobit tak, aby prováděly složité úkoly bez nutnosti výměny nástroje. Platforma Java nabízí širokou škálu možností dynamického spouštěcího modulu.

HotJava a další horké možnosti

Podívejme se stručně na některé z dostupných možností dynamického spouštěcího modulu a poté se podívejme do hloubky na implementaci mého tlumočníka. Dynamický spouštěcí modul je vložený tlumočník. Aby mohl tlumočník pracovat, potřebuje tři zařízení:

  1. Prostředek na načtení pokynů
  2. Formát modulu pro ukládání instrukcí, které mají být provedeny
  3. Model nebo prostředí pro interakci s hostitelským programem

HotJava

Nejznámějším vestavěným tlumočníkem musí být prostředí „appletu“ HotJava, které zcela změnilo způsob, jakým se lidé dívají na webové prohlížeče.

Model „appletu“ HotJava byl založen na představě, že aplikace Java může vytvořit obecnou základní třídu se známým rozhraním a poté dynamicky načítat podtřídy této třídy a spouštět je za běhu. Tyto applety poskytly nové funkce a v mezích základní třídy poskytly dynamické provádění. Tato schopnost dynamického provádění je základní součástí prostředí Java a jednou z věcí, díky nimž je tak výjimečná. Na toto konkrétní prostředí se podíváme hlouběji v pozdějším sloupci.

GNU EMACS

Než dorazila HotJava, možná nejúspěšnější aplikací s dynamickým spuštěním byl GNU EMACS. Makro jazyk tohoto editoru podobný LISP se stal základem mnoha programátorů. Stručně řečeno, prostředí EMACS LISP se skládá z tlumočníka LISP a mnoha funkcí editačního typu, které lze použít k vytvoření nejsložitějších maker. Nemělo by být překvapením, že editor EMACS byl původně napsán v makrech určených pro editor s názvem TECO. Dostupnost bohatého (pokud nečitelného) jazyka maker v TECO tedy umožnila vytvoření zcela nového editoru. Dnes je GNU EMACS základním editorem a celé hry nebyly napsány v ničem jiném než v kódu EMACS LISP, známém jako el-code. Díky této možnosti konfigurace se GNU EMACS stal hlavním pilířem, zatímco terminály VT-100, na kterých byl navržen, se staly pouhými poznámkami pod čarou ve sloupci spisovatele.

REXX

Jedním z mých oblíbených jazyků, který nikdy neudělal takový rozruch, jaký si zasloužil, byl REXX, navržený Mike Cowlishawem z IBM. Společnost potřebovala jazyk pro ovládání aplikací na velkých sálových počítačích s operačním systémem VM. REXX jsem objevil na Amize, kde byl úzce spojen s širokou škálou aplikací prostřednictvím "portů REXX." Tyto porty umožňovaly vzdálené řízení aplikací pomocí překladače REXX. Toto propojení tlumočníka a aplikace vytvořilo mnohem výkonnější systém, než jaký byl možný u jeho komponent. Naštěstí jazyk žije v NETREXX, verzi, kterou napsal Mike a která byla zkompilována do Java kódu.

Když jsem se díval na NETREXX a mnohem starší jazyk (LISP v Javě), napadlo mě, že tyto jazyky tvoří důležitou součást příběhu aplikace Java. Jaký lepší způsob, jak vyprávět tuto část příběhu, než dělat tu něco zábavného - například vzkřísit BASIC-80? Ještě důležitější je, že by bylo užitečné ukázat jeden způsob, jakým lze skriptovací jazyky psát v jazyce Java, a prostřednictvím jejich integrace s jazykem Java ukázat, jak mohou zlepšit možnosti vašich aplikací Java.

ZÁKLADNÍ požadavky na vylepšení vašich aplikací Java

BASIC je jednoduše řečeno základní jazyk. Existují dvě myšlenkové směry, jak by se dalo napsat o tom tlumočníkovi. Jedním z přístupů je napsat programovací smyčku, ve které program tlumočníka načte jeden řádek textu z interpretovaného programu, analyzuje jej a poté zavolá podprogram k jeho provedení. Pořadí čtení, syntaktické analýzy a provádění se opakuje, dokud jeden z příkazů interpretovaného programu neřekne tlumočníkovi, aby se zastavil.

Druhým a mnohem zajímavějším způsobem, jak ve skutečnosti projekt řešit, je analyzovat jazyk na analyzovaný strom a poté spustit analyzovaný strom „na místě“. Takto fungují tokenizující tlumočníci a způsob, jakým jsem se rozhodl pokračovat. Tokenizační tlumočníci jsou také rychlejší, protože nepotřebují znovu skenovat vstup při každém provedení příkazu.

Jak jsem již zmínil výše, tři komponenty nezbytné k dosažení dynamického provedení jsou prostředky načítání, formát modulu a prostředí provádění.

První komponenta, prostředek k načtení, bude řešen prostředím Java InputStream. Jelikož vstupní toky jsou základní v I / O architektuře Java, je systém navržen tak, aby četl v programu z InputStream a převést jej do spustitelného formátu. To představuje velmi flexibilní způsob vkládání kódu do systému. Samozřejmě, protokol pro data procházející vstupním proudem bude ZÁKLADNÍ zdrojový kód. Je důležité si uvědomit, že lze použít jakýkoli jazyk; nedělejte chybu, když si myslíte, že tuto techniku ​​nelze na vaši aplikaci použít.

Po zadání zdrojového kódu interpretovaného programu do systému systém převede zdrojový kód na interní reprezentaci. Jako interní formát reprezentace pro tento projekt jsem se rozhodl použít syntaktický strom. Jakmile je analyzovaný strom vytvořen, lze s ním manipulovat nebo ho spustit.

Třetí složkou je prostředí pro provádění. Jak uvidíme, požadavky na tuto komponentu jsou poměrně jednoduché, ale implementace má několik zajímavých zvratů.

Velmi rychlá ZÁKLADNÍ prohlídka

Pro ty z vás, kteří možná o BASICu nikdy neslyšeli, poskytnu vám krátký pohled na jazyk, abyste mohli porozumět výzvám analýzy a provádění, které vás čekají. Další informace o BASICu velmi doporučuji na konci tohoto sloupce.

BASIC znamená Univerzální symbolický instruktážní kód pro začátečníky a byl vyvinut na univerzitě v Dartmouthu za účelem výuky výpočetních konceptů pro vysokoškoláky. Od svého vývoje se BASIC vyvinul do různých dialektů. Nejjednodušší z těchto dialektů se používá jako řídicí jazyky pro řadiče průmyslových procesů; nejsložitějšími dialekty jsou strukturované jazyky, které obsahují některé aspekty objektově orientovaného programování. Pro svůj projekt jsem si vybral dialekt známý jako BASIC-80, který byl na konci sedmdesátých let populární v operačním systému CP / M. Tento dialekt je jen mírně složitější než nejjednodušší dialekty.

Syntaxe výpisu

Všechny řádky příkazů jsou ve tvaru

[ : [ : ... ] ]

kde „Řádek“ je číslo řádku výpisu, „Klíčové slovo“ je klíčové slovo příkazu ZÁKLAD a „Parametry“ je sada parametrů spojených s daným klíčovým slovem.

Číslo řádku má dva účely: Slouží jako popisek pro příkazy, které řídí tok provádění, například a jít do příkaz a slouží jako třídicí značka pro příkazy vložené do programu. Jako značka řazení číslo řádku usnadňuje prostředí pro úpravy řádků, ve kterém jsou úpravy a zpracování příkazů smíchány v jedné interaktivní relaci. Mimochodem, toto bylo vyžadováno, když vše, co jste měli, byl dálnopis. :-)

I když to není příliš elegantní, čísla řádků dávají prostředí tlumočníka možnost aktualizovat program po jednom příkazu. Tato schopnost vychází ze skutečnosti, že příkaz je jednou analyzovanou entitou a může být propojen v datové struktuře s čísly řádků. Bez čísel řádků je často nutné při změně řádku znovu analyzovat celý program.

Klíčové slovo identifikuje příkaz BASIC. V tomto příkladu náš tlumočník podpoří mírně rozšířenou sadu základních klíčových slov, včetně jít do, gosub, vrátit se, tisk, -li, konec, data, obnovit, číst, na, rem, pro, další, nechat, vstup, stop, ztlumit, náhodně, tron, a troff. Je zřejmé, že v tomto článku se nebudeme věnovat všem těmto tématům, ale v mé „měsíční hloubce“ pro příští měsíc bude k dispozici dokumentace, kterou budete moci prozkoumat.

Každé klíčové slovo má sadu zákonných parametrů klíčových slov, které jej mohou sledovat. Například jít do za klíčovým slovem musí následovat číslo řádku, znak -li za výrazem musí následovat podmíněný výraz i klíčové slovo pak -- a tak dále. Parametry jsou specifické pro každé klíčové slovo. Pár těchto seznamů parametrů podrobně pojedu o něco později.

Výrazy a operátory

Parametr uvedený v příkazu je často výrazem. Verze BASIC, kterou zde používám, podporuje všechny standardní matematické operace, logické operace, umocňování a knihovnu jednoduchých funkcí. Nejdůležitější složkou výrazové gramatiky je schopnost volat funkce. Samotné výrazy jsou poměrně standardní a podobné těm, které jsou analyzovány příkladem v mém předchozím sloupci StreamTokenizer.

Proměnné a datové typy

Jedním z důvodů, proč je BASIC tak jednoduchým jazykem, je to, že má pouze dva datové typy: čísla a řetězce. Některé skriptovací jazyky, například REXX a PERL, tento rozdíl mezi datovými typy ani nedělají, dokud se nepoužívají. V BASICu se ale k identifikaci datových typů používá jednoduchá syntaxe.

Názvy proměnných v této verzi jazyka BASIC jsou řetězce písmen a čísel, které vždy začínají písmenem. Proměnné nerozlišují velká a malá písmena. A, B, FOO a FOO2 jsou tedy všechny platné názvy proměnných. Kromě toho je v BASICu proměnná FOOBAR ekvivalentní FooBaru. K identifikaci řetězců je k názvu proměnné připojen znak dolaru ($); proměnná FOO $ je tedy proměnná obsahující řetězec.

Nakonec tato verze jazyka podporuje pole používající ztlumit klíčové slovo a proměnná syntaxe formuláře JMÉNO (index1, index2, ...) až pro čtyři indexy.

Struktura programu

Programy v ZÁKLADĚ začínají ve výchozím nastavení na nejnižším číslovaném řádku a pokračují, dokud již nebudou zpracovány žádné další řádky nebo stop nebo konec klíčová slova jsou spuštěna. Níže je uveden velmi jednoduchý základní program:

100 REM Toto je pravděpodobně kanonický ZÁKLADNÍ příklad 110 REM programu. Upozorňujeme, že příkazy REM jsou ignorovány. 120 TISK „Toto je testovací program.“ 130 TISK "Sčítání hodnot mezi 1 a 100" 140 LET celkem = 0,150 PRO I = 1 AŽ 100 160 LET celkem = celkem + i 170 DALŠÍ I 180 TISK "Součet všech číslic mezi 1 a 100 je" celkem 190 KONEC 

Čísla řádků výše označují lexikální pořadí příkazů. Když jsou spuštěny, řádky 120 a 130 tisknou zprávy na výstup, řádek 140 inicializuje proměnnou a smyčka v řádcích 150 až 170 aktualizuje hodnotu této proměnné. Nakonec se výsledky vytisknou. Jak vidíte, BASIC je velmi jednoduchý programovací jazyk, a proto je ideálním kandidátem pro výuku výpočetních konceptů.

Organizace přístupu

Typické pro skriptovací jazyky, BASIC zahrnuje program složený z mnoha příkazů, které běží v konkrétním prostředí. Výzvou v oblasti designu je tedy konstrukce objektů tak, aby takový systém implementovaly užitečným způsobem.

Když jsem se podíval na problém, vyskočila na mě přímá datová struktura. Tato struktura je následující:

Veřejné rozhraní pro skriptovací jazyk se skládá z

  • Tovární metoda, která bere zdrojový kód jako vstup a vrací objekt představující program.
  • Prostředí, které poskytuje rámec, ve kterém se program spouští, včetně zařízení „I / O“ pro vstup a výstup textu.
  • Standardní způsob úpravy tohoto objektu, možná ve formě rozhraní, který umožňuje kombinaci programu a prostředí pro dosažení užitečných výsledků.

Interně byla struktura tlumočníka trochu komplikovanější. Otázkou bylo, jak postupovat při rozdělování dvou aspektů skriptovacího jazyka, analýze a provádění? Výsledkem byly tři skupiny tříd - jedna pro analýzu, druhá pro strukturální rámec reprezentace analyzovaných a spustitelných programů a druhá, která vytvořila základní třídu prostředí pro provádění.

Ve skupině analýzy jsou vyžadovány následující objekty:

  • Lexikální analýza pro zpracování kódu jako textu
  • Analýza výrazů, konstrukce analyzovaných stromů výrazů
  • Syntaktická analýza výpisů, konstrukce analyzovaných stromů samotných příkazů
  • Chybové třídy pro hlášení chyb při analýze

Skupina rozhraní se skládá z objektů, které obsahují parsovací stromy a proměnné. Tyto zahrnují:

  • Objekt prohlášení s mnoha specializovanými podtřídami, které představují analyzované příkazy
  • Objekt výrazu, který představuje výrazy pro vyhodnocení
  • Proměnný objekt s mnoha specializovanými podtřídami, které představují atomické instance dat