【ITニュース解説】Day 1 - Setting up the Scanner

2025年09月05日に「Dev.to」が公開したITニュース「Day 1 - Setting up the Scanner」について初心者にもわかりやすいように丁寧に解説しています。

作成日: 更新日:

ITニュース概要

プログラミング言語Loxのインタープリタ開発初日。コードを読み込み、キーワードや識別子といった最小単位「トークン」に分解する「スキャナ」を実装した。スキャナは入力されたプログラムを解析し、トークンリストを生成する。現在は単一文字や比較演算子に対応し、今後はより複雑な要素を扱う予定だ。

出典: Day 1 - Setting up the Scanner | Dev.to公開日:

ITニュース解説

プログラミング言語が書かれたソースコードをコンピューターが理解し、実行するためには、いくつかの段階を踏む必要がある。その最初の、そして非常に重要なステップの一つが「スキャナー」と呼ばれる処理だ。このニュース記事は、Loxという架空のプログラミング言語のインタープリタを構築する初期段階において、スキャナーがどのように機能するかを具体的に解説している。

インタープリタとは、ソースコードを直接解釈して実行するプログラムのことで、書かれたコードを一行ずつ読み込み、その場で意味を解釈して実行する仕組みを持つ。この処理を開始するにあたり、まずソースコードの文字列を、プログラムが理解しやすい意味のある単位に分解する必要がある。この分解作業を担うのが、字句解析器、またはスキャナーと呼ばれる部分だ。

スキャナーの主な役割は、プログラマーが記述したソースコードの文字列を、プログラミング言語にとって意味を持つ最小単位である「トークン」の列に変換することにある。ソースコードはコンピューターにとってはただの文字の羅列だが、スキャナーはそれを一つ一つの意味を持つ部分に切り分け、種類を特定する。例えば、「if (x == 10)」というコードがあった場合、スキャナーはこれを「if」というキーワード、「(」という左括弧、「x」という変数名、「==」という等号演算子、「10」という数値、「)」という右括弧といった個別のトークンに分解する。

トークンはプログラミング言語の「最小の有意味な単位」であり、言語が持つ特別な意味を持つ「キーワード」(ifelsewhileなど)、プログラマーが定義する「識別子」(変数名、関数名など)、数値や文字列などの「リテラル」(具体的な値)、そして括弧や演算子といった「記号」などが含まれる。スキャナーは、これらのトークン一つ一つに対して、それがどのような種類(タイプ)であるかを識別し、その情報を持つオブジェクトとして後続の処理に渡せるように準備する。

この記事で構築されているLox言語のインタープリタの初期部分では、主に四つの要素が中心的な役割を果たす。それはLoxクラス、Scannerクラス、Tokenクラス、そしてTokenTypeだ。

まず、Loxクラスはインタープリタ全体の入り口として機能する。ユーザーが入力したソースコード(ファイルから読み込まれるか、直接入力されるか)を受け取り、それを処理するための中心的な役割を担う。そして、このLoxクラスがScannerオブジェクトを生成し、実際の字句解析の処理を開始させる。

次に、Scannerクラスが字句解析の主要な処理を行う部分だ。ソースコードの文字列を一文字ずつ読み込み、「字句(lexeme)」と呼ばれる生のコード単位を識別する。字句とは、まだトークンタイプが割り当てられていない、ソースコードから切り出された単なる文字列の断片を指す。このScannerクラスは、識別した字句がどのトークンタイプに属するかを判断し、対応するTokenオブジェクトを生成していく。最終的に、Scannerは生成されたTokenオブジェクトのリストを作成し、後続の処理(例えば、コードの構造を理解するための構文解析など)に渡せるようにする。

Tokenクラスは、スキャナーによって生成された個々のトークンを表すためのデータ構造だ。各Tokenオブジェクトは、そのトークンがどの種類であるかを示す「タイプ」、ソースコード上の元の文字列である「字句(lexeme)」、そして数値や文字列リテラルの場合はその実際の値を示す「リテラル」という三つの主要な情報を持つ。例えば、数値「123」という字句に対しては、タイプが「数値リテラル」、字句が「"123"」、リテラルが「123」という整数値となる。

そして、TokenTypeは、Lox言語で利用可能なすべてのトークンの種類を定義する「列挙型(enum)」だ。これは、例えばLEFT_PAREN(左括弧)、RIGHT_PAREN(右括弧)、EQUAL_EQUAL(等号演算子==)、IDENTIFIER(識別子)、NUMBER(数値)など、言語が認識するトークンのカテゴリをあらかじめ列挙したものだ。Scannerは、字句を読み込んだ際に、このTokenTypeに定義されたいずれかのタイプにマッチするかどうかを判断する。

スキャナーの具体的な動作は、ソースコードを先頭から順に読み進めることで行われる。現在の処理位置を追跡するための「オフセットカウンター」を維持しながら、advance()というメソッドで一文字ずつ次の文字を読み込む。読み込んだ文字に対して、scanToken()というメソッドが起動され、switch-case文を用いてその文字の種類を判別する。

例えば、(という文字を読み込んだ場合、switch-case文はそれをLEFT_PARENというトークンタイプとして認識し、addToken(LEFT_PAREN)という形でTokenオブジェクトを生成してトークンリストに追加する。同様に、{であればLEFT_BRACE}であればRIGHT_BRACEといった具合だ。

少し複雑になるのが、!=といった文字だ。これらは単独で意味を持つ場合(!は否定、=は代入)と、次の文字と組み合わされて異なる意味を持つ場合(!=は不等号、==は等号比較)がある。このようなケースに対応するため、スキャナーはmatch('=')のような仕組みを使う。これは、現在の文字の次に特定の文字が続くかどうかを確認するもので、もし続く場合は組み合わせたトークンタイプ(例えばBANG_EQUAL)を、続かない場合は単独のトークンタイプ(BANG)を生成する。この仕組みにより、スキャナーはより正確に字句をトークンにマッピングできる。

エラーハンドリングについては、スキャナーの処理をできるだけシンプルに保つため、Scannerクラス本体からは分離されている。字句解析中に予期しない文字が見つかるなど、エラーが発生した場合は、Lox.error()のような静的なメソッドを呼び出し、発生した行番号、列番号、そしてエラーメッセージを渡すことで、エラーの報告が行われる。これは、それぞれのクラスが自身の専門的な役割に集中できるようにするための、優れた設計原則だ。

現在の実装では、主に単一文字で構成される字句(括弧、波括弧など)と、!===のような一部の比較演算子のみを扱っている。今後のステップとしては、より長い字句、例えばキーワード(whileforなど)や識別子(変数名)、そして数値や文字列などのリテラルに対応できるよう、スキャナーの機能を拡張していく計画だ。

このように、スキャナーはプログラミング言語のインタープリタやコンパイラがソースコードを理解するための最初の、そして極めて重要なステップを担う。無数の文字の羅列から、意味を持つ最小単位であるトークンを正確に切り出し、分類することで、その後の高度な解析や実行の土台を築いている。

【ITニュース解説】Day 1 - Setting up the Scanner | いっしー@Webエンジニア