構文(プログラミング言語)

ウィキペディアから、無料の百科事典
ナビゲーションにジャンプ 検索にジャンプ
構文の強調表示インデントスタイルは、プログラマーがソースコードの要素を認識するのを支援するためによく使用されます。このPythonコードは、色分けされた強調表示を使用しています。

コンピュータサイエンスは、コンピュータ言語の構文は、その言語で正しく構造化されたステートメントまたはと見なされるシンボルの組み合わせを定義する一連のルールです。これは、ドキュメントがソースコード表すプログラミング言語と、ドキュメントがデータを表すマークアップ言語の両方に適用されます。

言語の構文は、その表面形式を定義します。[1] テキストベースのコンピューター言語は文字のシーケンスに基づいていますが、ビジュアルプログラミング言語は空間レイアウトと記号間の接続(テキストまたはグラフィックの場合があります)に基づいています。構文的に無効なドキュメントは、構文エラーがあると言われます言語の構文を設計するとき、設計者は、これらの例から一般的な規則を理解しようとする前に、有効な文字列と不正な文字列の両方の例を書き留めることから始める場合があります。[2]

したがって、構文はコードの形式を参照し、セマンティクス意味)とは対照的です。コンピュータ言語の処理では、セマンティック処理は通常、構文処理の後に行われます。ただし、場合によっては、完全な構文解析のためにセマンティック処理が必要であり、これらは一緒にまたは同時に実行されます。コンパイラーでは、構文解析はフロントエンドを構成し、意味分析バックエンド(およびこのフェーズが区別される場合はミドルエンド)を構成します。

構文のレベル

コンピュータ言語の構文は、一般的に3つのレベルに区別されます。

  • 単語–字句レベル、文字がトークンを形成する方法を決定します;
  • フレーズ–文法レベル、狭義には、トークンがフレーズを形成する方法を決定します。
  • コンテキスト–タイプが有効かどうかなど、オブジェクトまたは変数の名前が参照するものを決定します。

このように区別することでモジュール性が生まれ、各レベルを個別に、多くの場合は独立して記述および処理できます。まず、レクサーは文字の線形シーケンスをトークンの線形シーケンスに変換します。これは「字句解析」または「字句解析」として知られています。次に、パーサーはトークンの線形シーケンスを階層構文ツリーに変換します。これは、狭義には「解析」として知られています。第三に、コンテキスト分析は名前を解決し、タイプをチェックします。このモジュール性は可能な場合もありますが、多くの実際の言語では、前のステップは後のステップに依存します。たとえば、 Cのレクサーハックはトークン化がコンテキストに依存するためです。このような場合でも、構文分析はこの理想的なモデルに近いものと見なされることがよくあります。

構文解析段階自体は、2つの部分に分けることができます。構文解析ツリー、つまり文法によって決定される「具体的な構文ツリー」ですが、実際にはあまりにも詳細であるため、実際に使用するには詳細すぎます。抽象構文ツリー(AST)は単純化されます。これを使用可能な形式にします。ASTおよびコンテキスト分析ステップは、構文に意味と解釈を追加するため、セマンティック分析の形式と見なすことができます。あるいは、正式に記述または実装するのが困難または厄介な構文規則の非公式の手動実装と見なすことができます。

レベルは通常、チョムスキー階層のレベルに対応します。単語は正規言語であり、字句文法で指定されます。これは、タイプ3の文法であり、通常は正規表現として与えられます。句は文脈自由言語(CFL)であり、一般に決定性文脈自由言語(DCFL)であり、句構造文法で指定されます。これはタイプ2文法であり、一般バッカス・ナウア形式(BNF )の生成規則として与えられます。 )。句文法は、構文解析を容易にするために、完全な文脈自由文法よりもはるかに制約のある文法で指定されることがよくあります。一方、LRパーサーは線形時間で任意のDCFLを解析でき、単純なLALRパーサーとさらに単純なLLパーサーの方が効率的ですが、生成ルールが制約されている文法のみを解析できます。原則として、文脈構造は文脈依存文法によって記述され、属性文法などの手段によって自動的に分析されますが、一般に、このステップは名前解決ルールと型チェックを介して手動で実行され、シンボルテーブルを介して実装されます各スコープの名前とタイプを格納します。

正規表現で記述された字句仕様からレクサーを自動的に生成し、BNFで記述されたフレーズ文法からパーサーを自動的に生成するツールが作成されました。これにより、手続き型プログラミングや関数型プログラミングではなく、宣言型プログラミングを使用できます。注目すべき例は、lexとyaccペアです。これらは自動的に具体的な構文木を生成します。次に、パーサーライターは、これがどのように抽象に変換されるかを説明するコードを手動で作成する必要があります。構文木。コンテキスト分析も通常、手動で実装されます。これらの自動ツールが存在するにもかかわらず、さまざまな理由で解析が手動で実装されることがよくあります。おそらく、句構造が文脈自由ではないか、代替の実装によってパフォーマンスやエラーレポートが改善されるか、文法をより簡単に変更できるようになります。パーサーは、Haskellなどの関数型言語、 PythonPerlなどのスクリプト言語、またはCC ++で記述されることがよくあります。

エラーの例

例として、(add 1 1)は構文的に有効なLispプログラムであり(「add」関数が存在すると仮定すると、名前解決は失敗します)、1と1を追加します。ただし、以下は無効です。

(_ 1 1)字句エラー: '_'は無効です
(1 1解析エラーを追加:終了がありません ')'

レクサーは最初のエラーを識別できないことに注意してください。トークンLEFT_PARENを生成した後、 '('単語ルールが '_'で始まらないため、プログラムの残りの部分は無効です。2番目のエラーが検出されます。解析段階で:パーサーは '('トークン(唯一の一致として)のために「リスト」生成ルールを識別したため、エラーメッセージを表示する可能性があります。一般にあいまいな場合があります。

型エラーと宣言されていない変数エラーは、コンパイル時に検出された場合(通常、強い型の言語をコンパイルする場合)、構文エラーと見なされることがありますが、これらの種類のエラーは代わりにセマンティックエラーとして分類されるのが一般的です。[3] [4] [5]

例として、Pythonコード

'a' + 1

文字列リテラルを整数リテラルに追加するため、型エラーが含まれています。この種のタイプエラーは、コンパイル時に検出できます。コンパイラが「integerLiteral + integerLiteral」を許可するが「stringLiteral + integerLiteral」は許可しない個別のルールを使用する場合、解析(フレーズ分析)中に検出できます。コンパイラーは、「LiteralOrIdentifier + LiteralOrIdentifier」の形式のすべての式を許可する構文解析ルールを使用します。その後、コンテキスト分析中にエラーが検出されます(型チェックが発生した場合)。場合によっては、この検証はコンパイラによって行われず、これらのエラーは実行時にのみ検出されます。

動的型付け言語では、型は実行時にのみ判別できますが、多くの型エラーは実行時にのみ検出できます。たとえば、Pythonコード

a + b

は句レベルで構文的に有効ですが、変数にはPythonの型がなく、値のみが存在するため、aとbの型の正確さは実行時にのみ判断できます。コンパイラによって検出された型エラーを(静的な意味エラーではなく)構文エラーと呼ぶべきかどうかについては意見の相違がありますが、プログラムの実行時にのみ検出できる型エラーは、構文エラーではなく常に意味エラーと見なされます。

構文定義

インセットトークン化を使用したPythonコードの解析ツリー

テキストプログラミング言語の構文は、通常、正規表現字句構造の場合)とバッカスナウア記法文法構造の場合)の組み合わせを使用して定義され、構文カテゴリ(非終端記号)と終端記号を誘導的に指定します構文カテゴリは、特定の構文カテゴリに属する​​値を指定するproductionsと呼ばれるルールによって定義されます。[1]終端記号は、具体的な文字または文字列です(たとえば、 defineifletvoidなどのキーワード))構文的に有効なプログラムが構築されます。

言語は、同等の正規表現(字句レベルで)などの異なる同等の文法、または同じ言語を生成する異なる句規則を持つことができます。LR文法など、より広いカテゴリの文法を使用すると、より多くのルールでより長い文法が必要になる可能性があるLL文法などのより制限されたカテゴリと比較して、より短いまたはより単純な文法が可能になります。基礎となる言語(有効なドキュメントのセット)は同じですが、異なるが同等のフレーズ文法は異なる解析ツリーを生成します。

例:LispS式

以下は、正規表現の表記法と拡張バッカス・ナウア記法を使用して定義された単純な文法です。S式の構文、プログラミング言語Lispのデータ構文について説明します。これは、構文カテゴリの原子数値記号、およびリストの生成を定義します。

= アトム   |  リスト
アトム       = 番号|  シンボル    
番号     =  [ + - ] [ '0' - '9' ] +
シンボル     =  [ 'A' - 'Z' ] [ 'A' - 'Z''0' - '9' ]。*
リスト       =  '('  *  ')'

この文法は以下を指定します:

  • アトムまたはリストのいずれかです。
  • 原子数字または記号のいずれかです。
  • 数値は、1つ以上の10進数の途切れのないシーケンスであり、オプションでプラスまたはマイナス記号が前に付きます。
  • 記号は、文字の後に0個以上の任意の文字(空白を除く)が続くものです。
  • リストは、一致する括弧のペアであり、その中には0個以上の含まれています。

ここで、10進数、大文字と小文字、および括弧は終端記号です。

以下は、この文法の整形式トークンシーケンスの例です。 ' 12345'、 ' ()'、 ' (A B C232 (1))'

複雑な文法

プログラミング言語を指定するために必要な文法は、チョムスキー階層内の位置によって分類できますほとんどのプログラミング言語のフレーズ文法は、タイプ2の文法を使用して指定できます。つまり、コンテキストフリーの文法です[6]が、全体的な構文はコンテキストに依存します(変数宣言とネストされたスコープのため)。したがって、タイプ- 1.1。ただし、例外があり、一部の言語では、フレーズ文法はタイプ0(チューリング完全)です。

PerlやLispのようないくつかの言語では、言語の仕様(または実装)により、構文解析フェーズ中に実行される構造が許可されます。さらに、これらの言語には、プログラマーがパーサーの動作を変更できるようにする構造があります。この組み合わせにより、構文解析と実行の区別が効果的に曖昧になり、これらの言語では構文解析が決定不可能な問題になります。つまり、構文解析フェーズが終了しない場合があります。たとえば、Perlでは、BEGINステートメントを使用して解析中にコードを実行することが可能であり、Perl関数プロトタイプは構文の解釈を変更する可能性があり、場合によっては残りのコードの構文の妥当性さえも変更する可能性があります。[7]口語的には、これは「PerlのみがPerlを解析できる」(コードは解析中に実行する必要があり、文法を変更できるため)、またはより強力に「PerlでさえPerlを解析できない」(決定できないため)と呼ばれます。同様に、構文によって導入されたLispマクロdefmacroも解析中に実行されます。つまり、LispコンパイラにはLispランタイムシステム全体が存在する必要があります。対照的に、Cマクロは単なる文字列置換であり、コードを実行する必要はありません。[8] [9]

構文とセマンティクス

言語の構文は、有効なプログラムの形式を記述しますが、プログラムの意味やそのプログラムの実行結果に関する情報は提供しません。シンボルの組み合わせに与えられた意味は、セマンティクス(正式またはリファレンス実装でハードコーディングされたもの)によって処理されます。すべての構文的に正しいプログラムが意味的に正しいわけではありません。それにもかかわらず、多くの構文的に正しいプログラムは、言語の規則に従って、形式が正しくありません。また、(言語仕様と実装の健全性に応じて)翻訳または実行時にエラーが発生する可能性があります。場合によっては、そのようなプログラムは未定義の動作を示す可能性がありますプログラムが言語内で明確に定義されている場合でも、それを作成した人が意図していない意味を持っている可能性があります。

自然言語例にとると、文法的に正しい文に意味を割り当てることができないか、文が間違っている可能性があります。

  • 無色の緑のアイデアは猛烈に眠ります。」文法的には整形式ですが、一般的に受け入れられている意味はありません。
  • 「ジョンは既婚の独身者です。」文法的には整形式ですが、真実ではありえない意味を表現しています。

次のC言語フラグメントは構文的には正しいですが、意味的に定義されていない操作を実行します(nullポインターであるため、操作は意味がありません)。 pp->realp->im

 複素数* p = NULL ;   
 複素数abs_p = sqrt p- >実数* p- >実数+ p- > im * p- > im );          

より簡単な例として、

 int x ; 
 printf "%d" x ); 

構文的には有効ですが、初期化されていない変数を使用するため、意味的に定義されていません一部のプログラミング言語(JavaやC#など)のコンパイラは、この種の初期化されていない変数エラーを検出しますが、構文エラーではなく、セマンティックエラーと見なす必要があります。[5] [10]

も参照してください

さまざまなプログラミング言語の構文をすばやく比較するには、「Hello、World!」のリストを参照してください。プログラムの例:

参考文献

  1. ^ a b フリードマン、ダニエルP。; ミッチェルワンド; クリストファーT.ヘインズ(1992)。プログラミング言語の要点(第1版)。MITプレス。ISBN 0-262-06145-7
  2. ^ スミス、デニス(1999)。保守可能なソフトウェアの設計シュプリンガーサイエンス&ビジネスメディア。
  3. ^ Aho、Alfred V。; モニカ・S・ラム; ラビ・セシィ; ジェフリー・D・ウルマン(2007)コンパイラ:原理、技術、およびツール(第2版)。アディソンウェスリー。ISBN 0-321-48681-1セクション4.1.3:構文エラー処理、pp.194–195。
  4. ^ Louden、Kenneth C.(1997)。コンパイラの構築:原則と実践ブルックス/コール。ISBN 981-243-694-4演習1.3、27〜28ページ。
  5. ^ a bJava のセマンティックエラー
  6. ^ Michael Sipser(1997)。計算理論の紹介PWSパブリッシング。ISBN 0-534-94728-Xセクション2.2:プッシュダウンオートマトン、pp.101–114。
  7. ^ 以下の議論は例を示します:
  8. ^ 「CommonLispマクロの紹介」Apl.jhu.edu。1996-02-08。2013年8月6日にオリジナルからアーカイブされまし2013年8月17日取得
  9. ^ 「CommonLispクックブック-マクロとバッククォート」Cl-cookbook.sourceforge.net。2007-01-16 2013年8月17日取得
  10. ^ 構文またはセマンティクスの問題?

外部リンク