Luiz Henrique de Figueiredo、Roberto Ierusalimschy、Waldemar Celes Filho 著
カスタマイズ可能なアプリケーションへの需要が高まっています。アプリケーションが複雑化するにつれ、単純なパラメータによるカスタマイズは不可能になりました。ユーザーは実行時に設定の決定を行うことを望み、生産性を向上させるためのマクロやスクリプトを作成することも望んでいます (Ryan 1990)。そのため、現在では、大規模アプリケーションはほぼ例外なく、エンドユーザープログラミングのための独自の構成言語またはスクリプト言語を備えています。これらの言語は通常シンプルですが、それぞれ独自の構文を持っています。その結果、ユーザーは各アプリケーションごとに新しい言語を学習する必要があり(開発者は設計、実装、デバッグを行う必要があります)。
独自のスクリプト言語を使用した最初の経験は、データ入力アプリケーションにおいてでした。このアプリケーションのために、非常にシンプルな宣言型言語が設計されました (Figueiredo–Souza–Gattass–Coelho 1992)。(データ入力は、事前にコード化された検証テストではあらゆるアプリケーションに適しているとは限らないため、ユーザー定義のアクションが特に必要とされる分野です。) ユーザーがこの言語にますます高度な機能を求めるようになったため、より一般的なアプローチが必要であると判断し、汎用埋め込み言語の設計を開始しました。同時に、別のアプリケーションにデータ記述のための別の宣言型言語が追加されようとしていました。そこで、これら2つの言語を1つに統合することを決定し、データ記述機能を備えた手続き型言語としてLuaを設計しました。Luaはその後、当初の用途を超えて成長し、他のいくつかの産業プロジェクトで使用されています。
本稿では、Luaの実装における設計上の決定と詳細について説明します。
アプリケーション拡張のための言語の使用は、重要な設計手法として認識されるようになりました。アプリケーションの設計を明確化し、ユーザーによる設定を可能にします。ほとんどの拡張言語はシンプルで、特定のタスクに特化しているため、「小さな言語」(little languages) (Bentley 1986; Valdés 1991) と呼ばれてきました。これは、アプリケーションが記述される「大きな」主流言語とは対照的です。アプリケーションの大部分が実際には拡張言語を使用して記述されているため、この区別は現在それほど明確ではありません。拡張言語にはいくつかの種類があります。
config.sys
、MS-Windowsの.ini
ファイル、X11リソースファイル、MotifのUILファイル)。埋め込み言語をスタンドアロン言語と異なるものにしているのは、埋め込み言語は埋め込まれた状態でホストクライアント(埋め込みプログラム)でのみ動作することです。さらに、ホストプログラムは通常、埋め込み言語にドメイン固有の拡張機能を提供できるため、独自の目的に合わせてカスタマイズされた埋め込み言語のバージョンを作成できます(より高レベルの抽象化を提供することにより)。そのため、ホストにパラメータ値とアクションシーケンスを提供するために使用されるより単純な拡張言語とは異なり、埋め込み言語とホストプログラムの間には双方向の通信があります。アプリケーションプログラマはホストプログラムに使用される主流言語で埋め込み言語とインターフェースしますが、ユーザーは埋め込み言語のみを使用してアプリケーションとインターフェースすることに注意してください。
LISPは、そのシンプルで簡単に解析できる構文と組み込みの拡張性から、拡張言語として常に人気のある選択肢でした (Beckman 1991; Nahaboo)。たとえば、Emacsの大部分は実際には独自のLISP変種で記述されています。他の多くのテキストエディタも同様のアプローチを取っています。しかし、カスタマイズに関してLISPをユーザーフレンドリーとは言えません。Cやシェル言語も同様です。後者はさらに複雑で、なじみのない構文を持っています。
Luaの設計において行われた基本的な決定の1つは、クリーンでなじみのある構文にすることでした。すぐに簡素化されたPascal風の構文を採用しました。LISPやCに基づく構文は避けました。なぜなら、外部の人やプログラミング初心者にとって気が滅入る可能性があったからです。したがって、Luaは主に手続き型言語です。しかし、既に述べたように、Luaは表現力を高めるためにデータ記述機能を獲得しました。
Luaは、データ記述機能を備えた手続き型プログラミングをサポートするように設計された、汎用埋め込みプログラミング言語です。埋め込み言語であるため、Luaには「メイン」プログラムの概念がありません。ホストクライアントに埋め込まれた状態でのみ動作します(Luaは、ホストアプリケーションにリンクされるC関数のライブラリとして提供されます)。ホストは、Luaでコードを実行する関数を呼び出し、Lua変数を書き込みおよび読み取り、Luaコードによって呼び出されるC関数を登録できます。登録されたC関数を使用すると、Luaを拡張してさまざまなドメインに対処できるため、構文フレームワークを共有するカスタマイズされたプログラミング言語を作成できます (Beckman 1991)。
このセクションでは、Luaの主要な概念の概要を説明します。言語の雰囲気をつかむために、実際のコードの例をいくつか示します。言語の正確な定義は、リファレンスマニュアル (Ierusalimschy–Figueiredo–Celes 1994) に記載されています。
前述のように、Luaはシンプルでなじみのある構文になるように明示的に設計されました。その結果、Luaは、暗黙的ですが明示的に終了されるブロック構造を持つ、ほぼ従来のステートメントセットをサポートしています。従来のステートメントには、単純な代入、while-do-end
、repeat-until
、if-then-elseif-else-end
などの制御構造、関数呼び出しなどがあります。従来のステートメントではないものには、複数代入、ブロック内のどこにでも配置できるローカル変数宣言、ユーザー定義の検証関数を含めることができるテーブルコンストラクターなどがあります(下記参照)。さらに、Luaの関数は可変数のパラメータを取ることができ、複数の値を返すことができます。これにより、複数の結果を返す必要がある場合に、参照によるパラメータの受け渡しは不要になります。
Luaのすべてのステートメントはグローバル環境で実行されます。すべてのグローバル変数と関数を保持するこの環境は、埋め込みプログラムの開始時に初期化され、終了するまで保持されます。グローバル環境は、LuaコードまたはLuaを実装するライブラリの関数を使用してグローバル変数を読み書きできる埋め込みプログラムによって操作できます。
Luaの実行単位はモジュールと呼ばれます。モジュールにはステートメントと関数定義を含めることができ、ファイル内またはホストプログラム内の文字列内に存在できます。モジュールが実行されると、最初にそのすべての関数とステートメントがコンパイルされ、関数がグローバル環境に追加されます。次に、ステートメントが順番に実行されます。モジュールがグローバル環境に加えるすべての変更は、その終了後も保持されます。これには、グローバル変数の変更と新しい関数の定義が含まれます(関数定義は実際にはグローバル変数への代入です。下記参照)。
Luaは動的型付け言語です。変数には型がありません。値のみに型があります。すべての値は独自の型を持ちます。したがって、言語には型定義がありません。変数の型宣言の不在は、一見些細な点ですが、実際には言語の簡素化において重要な要素です。これは、拡張言語として使用するために変更された多くの型付き言語のバリアントにおける主要な機能として頻繁に提示されています。さらに、Luaにはガベージコレクションがあります。使用されている値を追跡し、使用されていない値を破棄します。これにより、メモリ割り当ての手動管理(プログラミングエラーの主な原因の1つ)の必要性がなくなります。Luaには7つの基本的なデータ型があります。
Luaはいくつかの自動的な型変換を提供します。算術演算に関与する文字列は、可能であれば数値に変換されます。逆に、文字列が必要な場合に数値が使用されると、その数値は文字列に変換されます。この強制型変換は、プログラムを簡素化し、明示的な変換関数の必要性を回避するため有用です。
グローバル変数は宣言する必要はありません。ローカル変数のみ宣言が必要です。明示的にローカルと宣言されていない限り、すべての変数はグローバルとみなされます。ローカル変数宣言は、ブロック内のどこにでも配置できます。したがって、ローカル変数のみが宣言され、これらの宣言を変数の使用の近くに置くことができるため、特定の変数がローカルかグローバルかを判断するのは通常簡単です。
最初の代入の前に、変数の値はnilです。したがって、Luaには初期化されていない変数はありません。これは、プログラミングエラーのもう1つの大きな原因です。ただし、nilに対して有効な操作は代入と等価性テストのみです(nilの主な特性は、他の値とは異なることです)。したがって、「実際の」値が必要なコンテキスト(例:算術式)で「初期化されていない」変数を使用すると、実行時エラーが発生し、プログラマにその変数が適切に初期化されていないことを知らせます。したがって、nilで変数を自動的に初期化する目的は、プログラマが変数の初期化を避けることを奨励するためではなく、Luaが実際に初期化されていない変数の使用を知らせることを可能にするためです。
関数はLuaでファーストクラスの値とみなされます。変数に格納したり、他の関数に引数として渡したり、結果として返したりできます。Luaで関数が定義されると、その本体はコンパイルされ、指定された名前のグローバル変数に格納されます。Luaは、LuaとCの両方で記述された関数を呼び出し(操作できます)。後者の型はCfunctionです。
userdata型は、任意の(void*
) CポインタをLua変数に格納できるようにするために提供されています。Luaで有効な操作は、代入と等価性テストのみです。
型 *table* は連想配列、つまり数値と文字列の両方でインデックス付けできる配列を実装しています。そのため、この型は通常の配列を表すだけでなく、シンボルテーブル、集合、レコードなどを表すためにも使用できます。レコードを表すために、Luaはフィールド名をインデックスとして使用します。言語は、a.name
を a["name"]
の構文糖として提供することにより、この表現をサポートしています。
連想配列は強力な言語構成要素です。多くのアルゴリズムは、必要なデータ構造とそれらを検索するためのアルゴリズムが言語によって提供されるため、非常に単純化されます(Aho–Kerninghan–Weinberger 1988; Bentley 1988)。たとえば、テキスト内の各単語の出現回数をカウントするプログラムのコアは、
table[word] = table[word] + 1単語のリストを検索する必要なく記述できます。(ただし、アルファベット順のレポートには実際にある程度の作業が必要です。なぜなら、テーブル内のインデックスはLua内部では任意の順序で並べられているからです。)
テーブルはさまざまな方法で作成できます。最も簡単な方法は通常の配列に対応しています。
t = @(100)このような式は、新しい空のテーブルになります。次元(上記の例では100)はオプションであり、初期テーブルサイズのヒントとして指定できます。初期次元とは無関係に、Luaのすべてのテーブルは必要に応じて動的に拡張されます。そのため、
t[200]
や t["day"]
を参照することは完全に有効です。各エントリを明示的に入力せずにテーブルを作成するための、リスト用(@[]
)とレコード用(@{}
)の2つの代替構文があります。たとえば、次のように要素を提供することでリストを作成する方がはるかに簡単です。
t = @["red", "green", "blue", 3]同等の明示的なコードよりも簡単です。
t = @() t[1] = "red" t[2] = "green" t[3] = "blue" t[4] = 3さらに、リストとレコードの作成時にユーザー関数を次のように提供することもできます。
t = @colors["red", "green", "blue", "yellow"] t = @employee{name="john smith", age=34}ここで、
colors
と employee
は、テーブル作成後に自動的に呼び出されるユーザー関数です。このような関数は、フィールド値の確認、デフォルトフィールドの作成、またはその他の副作用に使用できます。したがって、employee
レコードのコードは次のコードと同等です。t = @() t.name = "john smith" t.age = 34 employee(t)Luaには型宣言がないにもかかわらず、テーブル作成後に自動的に呼び出されるユーザー関数の可能性により、Luaにはユーザーが制御する *型コンストラクタ* が実際に提供されます。この非従来の構成要素は非常に強力な機能であり、Luaを使用した宣言型プログラミングの表現です。
Luaを実装するライブラリにはAPI、つまりLuaをホストプログラムとインターフェースするためのC関数のセットがあります(このような関数は約30個あります)。これらの関数はLuaを埋め込み言語として特徴付け、ファイルまたは文字列に含まれるLuaコードの実行、CとLuaの間での値の変換、グローバル変数に含まれるLuaオブジェクトの読み書き、Lua関数の呼び出し、Luaによって呼び出されるC関数の登録(エラーハンドラを含む)などのタスクを処理します。単純なLuaインタプリタは次のように記述できます。
#include "lua.h" int main(void) { char s[1000]; while (gets(s)) lua_dostring(s); return 0; }この単純なインタプリタは、Cで記述されたドメイン固有の関数で拡張し、API関数
lua_register
を使用してLuaで使用できるようにすることができます。拡張関数は、Luaに値を受け渡し返すプロトコルに従います。Luaの事前定義関数のセットは小さくても強力です。それらのほとんどは、言語に一定の反射性を与える機能を提供します。そのような機能は、言語の他の部分や標準APIではシミュレートできません。事前定義関数は、ファイルまたは文字列に含まれるLuaモジュールの実行、テーブルのすべてのフィールドの列挙、すべてのグローバル変数の列挙、型クエリと変換などのタスクを処理します。
一方、ライブラリは、標準APIを介して直接実装される便利なルーチンを提供します。そのため、言語に必要ではなく、別々のCモジュールとして提供され、必要に応じてアプリケーションにリンクできます。現在、文字列操作、数学関数、入出力のためのライブラリがあります。
列挙関数は、Lua内のグローバル環境の永続性を提供するために使用できます。つまり、実行されるとすべてのグローバル変数の値を復元するLuaコードを書き込むLuaコードを記述できます。ここでは、言語自体で記述されたテキストファイルをストレージメディアとして使用して、Luaで値を保存および取得するいくつかの方法を示します。このように保存された値を復元するには、出力ファイルを実行するだけで十分です。
名前付きの単一の値を保存するには、次のコードで十分です。
function store(name, value) write(name .. '=') write_value(value) endここで、"
..
"は文字列連結演算子であり、write
は出力用のライブラリ関数です。関数write_value
は、事前定義関数type
によって返される文字列を使用して、その型に基づいて値の適切な表現を出力します。function write_value(value) local t = type(value) if t = 'nil' then write('nil') elseif t = 'number' then write(value) elseif t = 'string' then write('"' .. value .. '"') end end
テーブルの保存はもう少し複雑です。まず、write_value
を次のように拡張します。
elseif t = 'table' then write_record(value)テーブルがレコードとして使用されていると仮定すると(つまり、循環参照がなく、すべてのインデックスが識別子である)、テーブルの値はテーブルコンストラクタを使用して直接記述できます。
function write_record(t) local i, v = next(t, nil) -- "next" enumerates the fields of t write('@{') -- starts constructor while i do store(i,v) i, v = next(t, i) if i then write(', ') end end write('}') -- closes constructor end
拡張言語は、常に何らかの方法でアプリケーションによって解釈されます。単純な拡張言語は、ソースコードから直接解釈できます。一方、埋め込み言語は通常、複雑な構文とセマンティクスを持つ強力なプログラミング言語です。埋め込み言語のより効率的な実装手法は、言語のニーズに合った *仮想マシン* を設計し、このマシン用の *バイトコード* に拡張プログラムをコンパイルし、次にバイトコードを解釈することで仮想マシンをシミュレートすることです(Betz 1988, 1991; Franks 1991)。Luaの実装にはこのハイブリッドアーキテクチャを選択しました。これには、ソースコードの直接解釈よりも次の利点があります。
このアーキテクチャはSmalltalk(Goldberg–Robson 1983; Budd 1987)(*バイトコード*という用語はここから借用されました)で最初に考案され、Pコードに基づく成功したUCSD Pascalシステムでも使用されました(Clark–Koehler 1982)。これらのシステムでは、仮想マシンのバイトコードは、複雑さを軽減し、移植性を向上させるために使用されました。この方法は、BCPLコンパイラの移植にも使用されました(Richards–Whitby-Strevens 1980)。
拡張プログラムのコンパイルコードは、*lex* と *yacc* などの標準ツールを使用して構築できます(Levine–Mason–Brown 1992)。70年代後半に広く利用可能になった優れたコンパイラ構築ツールの存在は、特にUnix環境で、いくつかの小さな言語が台頭した主な理由でした。Luaの実装では、構文解析に *yacc* を使用しています。当初、字句解析器を *lex* を使用して記述しました。本番プログラムでのパフォーマンスプロファイルの後、このモジュールが拡張プログラムのロードと実行に必要な時間のほぼ半分を占めていることがわかりました。そこで、このモジュールをCで直接書き直しました。新しい字句解析器は、古いものよりも2倍以上の速度です。
Luaの実装で使用される仮想マシンは *スタックマシン* です。つまり、ランダムアクセスメモリがありません。すべてのテンポラリ値とローカル変数はスタックに保持されます。さらに、汎用レジスタはなく、スタックとプログラムの実行を制御する特別な制御レジスタのみがあります。これらのレジスタは、*スタックのベース*、*スタックのトップ*、*プログラムカウンタ* です。
仮想マシンのプログラムは、*バイトコード* と呼ばれる一連の命令です。プログラムの実行は、バイトコードの解釈によって実現され、それぞれがスタックの上部を操作する命令に対応します。たとえば、ステートメント
a = b + f(c)は次のようにコンパイルされます。
PUSHGLOBAL "b" PUSHGLOBAL "f" PUSHMARK PUSHGLOBAL "c" CALLFUNC ADJUST 2 ADD STOREGLOBAL "a"Luaの仮想マシンには約60個の命令があり、したがって8ビットのバイトコードを使用できます。多くの命令(例:
ADD
)は追加のパラメータを必要としません。これらの命令はスタックを直接操作し、コンパイルされたコードでちょうど1バイトを占めます。他の命令(例:PUSHGLOBAL
と STOREGLOBAL
)は追加のパラメータを必要とし、1バイト以上を占めます。パラメータは1、2、または4バイトを占めるため、一部のアーキテクチャでアライメントの問題が発生します。これは、NOP
を使用してアライメント境界までパディングすることで解決されます。多くの命令は最適化のためだけに存在します。たとえば、パラメータとして数値を受け取り、スタックにプッシュするPUSH
命令がありますが、ゼロや1などの一般的な値をプッシュするための1バイトの最適化されたバージョンもあります。そのため、PUSHNIL
、PUSH0
、PUSH2
、PUSH3
があります。このような最適化により、コンパイルされたバイトコードに必要なスペースと命令の解釈に必要な時間の両方が削減されます。
Luaは複数の代入と関数からの複数の戻り値をサポートしていることを思い出してください。そのため、場合によっては、値のリストをランタイムで特定の長さに *調整* する必要があります。必要な値よりも多くの値がある場合は、余分な値は破棄されます。必要な値よりも少ない値しかない場合は、リストは必要な数の **nil** で拡張されます。調整は、ADJUST
命令を使用してスタックで行われます。
複数の代入と戻り値はLuaの強力な機能ですが、コンパイラとインタプリタの両方にとって複雑さの重要な原因でもあります。関数に型宣言がないため、コンパイラは関数がいくつ値を返すかを知りません。そのため、調整はランタイムで行う必要があります。同様に、コンパイラは関数がいくつのパラメータを受け入れるかを知りません。この数はランタイムで異なる可能性があるため、パラメータのリストはPUSHMARK
命令とCALLFUNC
命令で囲まれています。
ホストによって提供される関数でLuaを拡張する1つの方法は、各関数にバイトコードを割り当てることです(Betz 1988)。この戦略によりインタプリタは簡素化されますが、Luaは8ビットのバイトコードを使用しており、プリミティブ命令に約60個を使用しているため、200個未満の外部関数を追加できるという欠点があります。ホストが外部関数を明示的に登録し、これらの関数をネイティブのLua関数のように処理するようにしました。したがって、単一のCALLFUNC
命令があり、インタプリタは呼び出される関数の型に基づいて何をすべきか決定します。
Franks(1991)によってかなり異なる戦略が提案されました。ホストの *すべての* 外部関数は、埋め込み言語によって呼び出すことができます。明示的な登録は必要ありません。これは、リンカによって生成されたマップを読み取って解釈することで実行されます。このソリューションはアプリケーションプログラマにとって非常に便利です。しかし、マップファイルの形式とオペレーティングシステムで使用される再配置戦略に依存しているため、移植性がありません(FranksはDOS用の特定のコンパイラを使用しました)。
前述のように、Luaの変数は型付けされていません。値のみが型付けされています。したがって、値は型と実際の値を含むunion
の2つのフィールドを持つstruct
で実装されています。これらのstruct
は、すべてのグローバルシンボルを保持するシンボルテーブルとスタックに存在します。
数値はunion
に直接格納されます。文字列は単一の配列に保持されます。型 *string* の値は、この配列へのポインタを含みます。型 *function* の値は、バイトコード配列へのポインタを含みます。型 *Cfunction* の値は、ホストプログラムによって提供されたC関数への実際のポインタを含みます。型 *userdata* の値についても同様です。
テーブルはハッシュテーブルとして実装されており、衝突はセパレートチェイニングによって処理されます(これが、テーブル内のインデックスが任意の順序付けられている理由です)。テーブル作成時に次元が指定されている場合、この次元はハッシュテーブルのサイズとして使用されます。したがって、テーブル内のインデックス数の予想値にほぼ等しい次元を指定することで、衝突が少なくなり、非常に効率的なインデックス検索が可能になります。さらに、テーブルを数値インデックスのみを使用する配列として使用する場合、作成時に適切な次元を選択することで、衝突が発生しないことが保証されます。
Lua の内部データ構造はすべて、動的に割り当てられた配列です。これらの配列に空きスロットがなくなると、標準的なマークアンドスイープアルゴリズムを使用してガベージコレクションが自動的に実行されます。すべての値が参照されているためスペースが回収されない場合、配列は現在のサイズの2倍に再割り当てされます。
ガベージコレクションは、明示的なメモリ管理を回避するため、プログラマにとって非常に便利です。Lua がスタンドアロン言語として使用される場合(頻繁に使用されます)、ガベージコレクションは利点となります。しかし、Lua がホストプログラムに組み込まれて使用される場合(これが主な目的です)、ガベージコレクションは、Lua とインターフェースする必要があるアプリケーションプログラマにとって新たな懸念事項をもたらします。Lua のテーブルや文字列を C 変数に格納しないように注意する必要があります。これらの値は、Lua の環境内でそれ以上の参照がない場合、ガベージコレクション中に回収される可能性があるためです。より正確には、プログラマはLuaへの制御を返す前に、これらの値を C 変数に明示的にコピーする必要があります。これは異なるパラダイムですが、標準 C ライブラリを使用したメモリ管理の一般的な `malloc`-`free` プロトコルよりも劣るものではありません。
Lua は1993年半ばから、以下のタスクのために広く本番環境で使用されてきました。
実行時に Lua プログラムを読み込んで実行する機能は、ユーザと開発者の両方にとって設定を容易にする主要なコンポーネントであることが証明されています。さらに、単一の汎用組み込み言語の存在は、非互換な言語の増殖を抑制し、アプリケーションに含まれる主要なテクノロジーとその設定の問題を明確に分離する、より優れた設計を促進します。
この論文で説明されている Lua の実装は、https://lua.dokyumento.jp/ftp/lua-1.1.tar.gz から匿名 FTP で入手できます。
Lua を使用してテストしてくれた ICAD と TeCGraf のスタッフに感謝いたします。本文で述べられている産業アプリケーションは、PETROBRAS(CENPES)と ELETROBRAS(CEPEL)の研究センターとのパートナーシップで開発されています。
M. Abrash, D. Illowsky, "Roll your own minilanguages with mini-interpreters", Dr. Dobb's Journal 14 (9) (Sep 1989) 52–72。
A. V. Aho, B. W. Kerninghan, P. J. Weinberger, The AWK programming language, Addison-Wesley, 1988。
B. Beckman, "A Scheme for little languages in interactive graphics", Software, Practice & Experience 21 (1991) 187–207。
J. Bentley, "Programming pearls: little languages", Communications of the ACM 29 (1986) 711–721。
J. Bentley, More programming pearls, Addison-Wesley, 1988。
D. Betz, "Embedded languages", Byte 13 #12 (Nov 1988) 409–416。
D. Betz, "Your own tiny object-oriented language", Dr. Dobb's Journal 16 (9) (Sep 1991) 26–33。
T. Budd, A Little Smalltalk, Addison-Wesley, 1987。
R. Clark, S. Koehler, The UCSD Pascal handbook: a reference and guidebook for programmers, Prentice-Hall, 1982。
M. Cowlishaw, The REXX programming language, Prentice-Hall, 1990。
L. H. de Figueiredo, C. S. de Souza, M. Gattass, L. C. G. Coelho, "Gera��o de interfaces para captura de dados sobre desenhos", Anais do SIBGRAPI V (1992) 169–175 [ポルトガル語]。
N. Franks, "Adding an extension language to your software", Dr. Dobb's Journal 16 (9) (Sep 1991) 34–43。
A. Goldberg, D. Robson, Smalltalk-80: the language and its implementation, Addison-Wesley, 1983。
R. Ierusalimschy, L. H. de Figueiredo, W. Celes Filho, "Reference manual of the programming language Lua", Monografias em Ci�ncia da Computa��o 4/94, Departamento de Inform�tica, PUC-Rio, 1994。
J. R. Levine, T. Mason, D. Brown, Lex & Yacc, O'Reilly and Associates, 1992。
C. Nahaboo, A catalog of embedded languages, available from colas@indri.inria.fr
。
M. Richards, C. Whitby-Strevens, BCPL: the language and its compiler, Cambridge University Press, 1980。
B. Ryan, "Scripts unbounded", Byte 15 (8) (Aug 1990) 235–240。
R. Vald�s, "Little languages, big questions", Dr. Dobb's Journal 16 (9) (Sep 1991) 16–25。