言語処理系とは?
言語処理系とは 「プログラミング言語の文法に従って 記述された文字列」(ソースコード)を読み取り、コンピュータが直接実行できる形式に変換したり、 そのまま実行するためのプログラムです。 変換して他の言語や実行可能形式のファイルを生成するものをコンパイラといい、 起動中のまま変換しながら実行するタイプの処理系をインタープリタといいます。
プログラミング言語は様々なものがありますが、それについては下記の「プログラミング言語の系譜」のところで解説します。
なお、言語処理系には日本語やロシア語など、自然言語を扱うものもありますが、 ここではプログラミング言語の処理系を中心に考えて行くことにします。
 
言語処理系が生まれた背景
今も昔もコンピュータの基本ロジックには、2つの数値に四則演算やビット演算を行い別の数値を作る、 そして外部のハードウェア(メモリやディスクなど)との数値の入出力、それだけしかありません。 それぞれの基本ロジックには番号が割り当てられていて、 コンピュータはその番号をメモリから読みとって次の処理を行うという作業を、高速で繰り返しているに過ぎません。 画像処理ソフトや、企業の基幹業務システムなど、一人の人間ではとても把握しきれないほど非常に大規模で複雑な 処理であっても、コンピュータはこれらの基本ロジックの組み合わせを実行しているに過ぎないのです。
言語処理系が生まれる以前は、コンピュータに処理させたい要件をこうした基本ロジックにまで分解するのは人間の仕事でした。 「請求書を発行したい」という実世界の要求に対して、プログラマはそれを大幅にかみ砕いて「 72番地の内容に21番の処理をして10以下だったら25番地へ行く。 32番地の内容と56番地の内容で6番の処理をし、それを98番へ…」 といったコンピュータが実行できるレベルの形式へと自分の脳ミソを使って変換し、 それを数値の羅列として入力しなければなりませんでした。 簡単な経理の計算を行うソフトを作るにしても、かなりの手間と神経を使うことが想像できるかと思います。
そこで昔からソフトウェアの開発効率を向上させるために様々な工夫が行われてきましたが、 その一つの手段として作られたのが言語処理系です。
人間の言葉を真似て作られたプログラミング言語を使うことで、ソフトウェア開発工程の見通しがつきやすくなり、 間違いが抑えられ、手間のかかる作業はできるだけコンピュータ自身に代行させることができるようになります。 また、言語処理系はその「ソフトウェアを作るためのソフトウェア」という性質から、 ソフトウェアに関わる技術全般と密接に関係して進歩してきたのです。
 
プログラミング言語の系譜
第1世代言語・機械語(machine language)
コンピュータは本質的にはこの言語(数値)しか実行できません。 これを使ってプログラムを書くと開発効率は著しく低いのですが、 十分に工夫して書けばコンピュータにとって非常に効率の良いプログラムを作ることができます。 またデバイスドライバなど、特定のハードウェアと頻繁にアクセスする必要があるプログラムを作る場合、 機械語で書いた方がシンプルになる場合もあります。
プログラムの大部分は他の高級言語(後述)で書いて、特に高速性が求められる部分だけを機械語で書く場合があります。 またコンパイラを作ろうとする場合は出力するものが機械語ですので、この知識は不可欠です。
 
第2世代言語・アセンブリ言語(assembly langauage)
機械語の命令やデータは全て数値で表されていますが、 これらの数値一つ一つに対して英数字の名前をつけたものがアセンブリ言語です。 本質的には機械語と変わりありませんが、数値を直接読み書きするのと比べれば、名前が付いているだけマシというものです。 また機械語でジャンプ命令を使う場合、「23番地へ行く」というように直接数値を打つ必要があったので、 プログラムの長さが変わったときなんかはジャンプ命令の行き先を丹念に調べて書き直さないといけません。 一個でも間違えるとコンピュータは支離滅裂な命令を実行し始めて、手の施しようがなくなります。 アセンブリ言語の場合、ラベルという機能を使ってジャンプ先の名前を指定しておけば、 処理系がジャンプ先を自動的に当てはめてくれますので、少しだけ気が楽です。
アセンブリ言語の処理系はアセンブラと呼ばれています。
 
第3世代言語・C, Pascal, FORTRAN, etc
第3世代以降のプログラミング言語は、別名「高級言語」と呼ばれていました。 今でもそう呼ぼうと思えば呼べるのですが、現在使われているプログラミング言語のほどんどがこれに当たるので、 あえて「高級」という名前で呼ばれることは少ないです。
機械語はコンピュータの基本ロジックそのもので、 アセンブリ言語は機械語の命令に名前を付けただけのものでした。 どちらもコンピュータの仕様にべったりとくっついた文法を使わなければいけません。 しかし第3世代言語以降のプログラミング言語は、文法がもっと人間の言葉に近くなり、分りやすくなります。
代表的な例として算術式の解析機能があります。「p=(m+v)÷2」という文字列を書けば、 処理系はその意味を計らって「変数mの数値と変数vの数値を加算し、その結果を定数2で割って、その結果を変数pに代入する」 という処理を自動的に作り出してくれます。 人間にとっては余りにも分りやすい式なので「何がそんなに難しいの?」と思われるかもしれませんが、 「p」とは何か、「=」とは何か、「(」とは何か、というように1文字ずつ個別に意味を確定し、 それから文の構造を割り出し、計算順序を確定するという処理を行わなければ、 この文字列を実行可能な状態に変換することはできません。
コンピュータには文字列も文字番号の並びに過ぎません。 文字番号を読み取って次に行うべき処理を決定するロジックは、人間が考えて書かないといけないのです。

この他にも高級言語では様々な概念が導入されています。以下に代表的なものを紹介します。

型と変数
機械語で開発する場合、プログラマは「メモリの23番地の内容は整数で、それと48番地の整数とを足すんだ」とか 「今は文字列の最後に別の文字列をくっつけるだ。まずは76番地から格納されているはずの文字列データを…」というように 頭の中でデータの形式をイメージして、基本ロジックを組み合わせてそれに見合った処理を作る必要があります。 コンピュータは、フラットなメモリ空間に数値の固まり(正確には0と1の組み合わせ)を保持しているに過ぎず、 それがどういう意味のデータで、どういう処理を施せば良いのかは人間にしか分らないからです。
それに対して高級言語では扱うデータの種類ごとに名前を付けて、それを「型」として扱えるようになりました。 整数型、文字列型、浮動小数点型、金額型、複素数型、など様々な型が言語仕様として用意されていて、 型から変数を宣言することで、型に見合ったデータ表現がメモリ上に用意されます。
変数は式の中で使えて、例えば「a + b」という式を書いた場合、aやbが整数だったら整数の加算処理を行い、 aやbが文字列だったら、aの後ろの領域にbの内容をコピーする、などといったように、 扱われる変数の型に見合った処理を高級言語の処理系は自動的に選択し、基本ロジックの組み合わせを作り出してくれるのです。 扱える型の種類や、異なる型の変数同士で計算を行う際の型変換の処理方法などは、 プログラミング言語ごとにそれぞれ違っていて個性があります。
またいくつもの変数をセットにして一つの変数として扱えるようにした「構造体」という型もあります。 それぞれの言語で記述方法に違いはありますが、 「住所の文字列と、名前の文字列、それと電話番号の文字列をセットにして、個人情報という型を作ります」というように 定義しておくと、あとは「個人情報」という型から変数を宣言することで、住所や名前や電話番号のデータを同時に用意 できます。
構造体変数の内部にアクセスする場合は、「の」を使います。 「個人情報型変数KATOの、文字列型変数TELEPHONEに“666-666-666”を代入する」 といったような記述になります。よりプログラミング言語に近い書き方をすると大抵の言語では、
KATO.TELEPHONE = "666-666-666"というような書き方になります。
関数
何行もの文を書いて実現させた機能を、名前一つで呼び出せる機能です。
関数を扱える処理系では通常、「画面に線を引く」「文字を表示する」「データベースにアクセスする」など、 アプリケーションソフトを作るための様々な機能を持った関数が初めから用意されています。
プログラマそれらの関数を組み合わせて自分が実現したい処理を作ることができますし、 そうして作った処理を新たな関数として定義することも可能です。
また関数を呼び出すときパラメータとして任意の変数を渡すことができます。「線を引く」関数なら、画面のどこからどこまで 「線を引く」のかを変数を使って関数の側に指定することができるのです。 逆に関数が何らかの処理をした後に結果報告として、任意の変数を呼び出した側に返すことができます。
(「任意」といういい方をしましたが、実際にはどんな変数が渡され、どんな変数が返されるのかは、 関数を定義する時に決めます。)
これらの機能のお陰で、プログラマがどういった処理をコンピュータに行わせたいのかを、 プログラムの文面から読み取ることがずっとやさしくなりました。
 
オブジェクト指向言語・C++, Java, SmallTalk, etc
第3世代言語で導入された「型」の概念(というよりは手段)をどんどん突き詰めて行くと、 最終的にはコンピュータが行うべき仕事の全体を、1つの型(クラス)から作られた変数(オブジェクト)に対する操作 (メソッド)として記述できるようになります。
この考え方に基づいて設計されたプログラミング言語がオブジェクト指向言語です。

オブジェクト指向では「クラス」という概念が中心的な役割を果たしますが、これが従来の高級言語で扱われていた 型を拡張した概念です。従来の高級言語にも、基本となる型を組み合わせて新たな型を作る、 「構造体」という機能がありました。 オブジェクト指向言語では、構造体型の構成要員として変数だけではなく関数も定義できるようになりました。 さらに、クラスが外部に公開している関数の呼び出しを通じてしか、 そのクラスのオブジェクトとアクセスできないようにすることができます。
従来の構造体は、単にデータの構造を表現できるだけで、そのデータ構造をどう処理するのかは、 外部の関数に任せきっていました。しかも構造体の内部には、どの関数からも無制限にアクセスできるため、 構造体型変数の意味や整合性はプログラム全体で管理する必要がありました。
しかし、オブジェクト指向言語のクラス型は、その意味や挙動を自分自身の関数によってコントロールできます。 単にデータの寄せ集めだった構造体型は、自分自身の意味を保証できる「抽象データ型」へと進化したのです。

クラスを使うことでプログラムはまた一段と分かりやすくなります。 機械語で「23番と74番に8番の処理を…」と書いていたのが、 高級言語からは「変数aと変数bを足して変数pに代入…」という書き方ができるようになりました。 そしてオブジェクト指向言語を使って十分にクラスの定義がなされている場合でしたら、 「売上履歴オブジェクトから今月のA社との取引内容を集計し、請求書印刷オブジェクトに対して、その結果の印刷を要求する」 という書き方ができるようになったのです。

  集計結果 = 売上履歴.集計(“A社”)
  請求書印刷.印刷(集計結果)
 
「売上履歴オブジェクト」の内部ではデータベース接続オブジェクトや、金額計算オブジェクト、 などといった様々なオブジェクトが共同作業を行い、全体として「売上履歴」という機能を実現していることでしょう。 そして各々の下請けオブジェクトの内部ではさらに細細とした手続きを行っているはずです。 しかしプログラマはそうした詳細を知らなくても、 最上位の2つのオブジェクトの外面だけの関係を記述することができます。
プログラマは使いたいオブジェクトが外部に公開している関数を呼び出せば、 その関数が、オブジェクト内部の関数や、構成部員のオブジェクトに指示(関数呼び出し)を出したりして、 そのオブジェクト(のクラス)が保証している動作を行わせることができます。
 
 
オブジェクト指向言語にはもう一つ「継承」と呼ばれる、強力な機能があります。
この機能を説明するために、例えばワープロソフトを作る場合を考えてみましょう。 ワープロでは文字の他にも直線や、四角などを描くことができます。
これらの図形は、大きさや位置の変更、色の変更、コピー&ペースト、保存と読みこみ、などといった 共通して行うことができる操作をもっています。 さらに、それぞれの図形はその大きさや位置や色に合わせて、 自分自身の画像を描画するという共通した機能を持っています。 また「グループ化」という機能を使えば、これらの図形を組み合わせることができ、組み合わされた 図形全体にも、先ほどの共通操作を行うことができます。
 
このように操作を共通しているクラス同士をまとめる機能が「継承」です。
この場合、まず「図形」という クラスを作り、移動、変形、色の変更、描画、といった共通の操作(関数)と、位置や大きさ、色、といった 共通の属性(変数)を定義しておきます。そして、直線クラスや、四角形クラス、 グループ化図形クラスなどのクラスを定義する際、この図形クラスから継承を行います。
こうすることで、直線や、四角形や、グループ化図形のオブジェクトを、等しく「図形」オブジェクトとして 扱うことができるようになります。 これらのクラスのオブジェクトを使用する側では、図形クラスのオブジェクトとして、 任意の操作、例えば「色の変更」関数を呼び出せば、 各々のクラスで定義されている「色の変更」関数が自動的に選択されて呼び出されます。
直線オブジェクトや四角形オブジェクトでしたら「色」属性を変更した後、 もう一度、自分の画像を描画し直せば、ユーザは色が変わったことを確認できます。 グループ化図形オブジェクトの場合、グループ化されている図形の全ての色を同時に変更するために、 全ての図形の「色の変更」関数を次々に呼び出すという処理が必要となるでしょう。
このように同じ操作でも各クラスごとに処理方法は変わってくるのが普通です。異なる処理を 行うためには異なる関数を呼び出す必要がありますが、継承を使って操作をまとめておけば、 クラスごとに別々に定義した関数であっても クラスの外部からは同じ関数として呼び出すことができるようになるのです。
こうしたオブジェクトによる関数の自動選択機能は「多態」と呼ばれています。
もしお使いのワープロソフトが多態をサポートしなくなったら、どうなることでしょう。 例えば「編集」メニューは「文字の切り取り、直線の切り取り、四角形の切り取り、 グループ化図形の切り取り、文字のコピー、直線のコピー、四角駅のコピー、……」 というようにオブジェクトの種類分だけメニュー項目が増えることになります。 そして文字が選択されている時に、「四角形の切り取り」を選択すると強制終了します。
こんなソフトは嫌ですよね?「複雑さ」には幾分慣れ親しんでいるプログラマにしても、こんなことは煩わしいに決まってます。
オブジェクト指向以前のプログラミング言語では、こうした複雑さを耐え忍んで開発を進める必要があったのです。 そこに醍醐味を感じるというプログラマもいますが…。
 
次のページへ
言語処理系の制作に戻る
もくじに戻る