
コンピュータソースコードの構文は、コンピュータ言語の規則に従って構造化され、順序付けられたコードです。自然言語と同様に、コンピュータ言語(つまりプログラミング言語)は、その言語で有効な構文を定義します。 [ 1 ]構文エラーは、コンパイラやインタプリタなどのツールによって構文的に無効なソースコードが処理されたときに発生します。
最も一般的に使用されている言語は、文字列に基づいた構文を持つテキストベースの言語です。一方、ビジュアルプログラミング言語の構文は、グラフィック要素間の関係に基づいています。
構文を設計する際、どの言語の設計者も、まず有効な文字列と無効な文字列の両方の例を書き出し、それらの例から一般的な規則を見つけ出そうとするかもしれません。構文の一般的な構造は、その構成形式によって決定され、考えられるすべての変更に対して常に意味的に有効な範囲が生成されます。そうでない場合は、無効な入力ごとにエラーと警告が返されます。[ 2 ]
コンピュータ言語の構文は、一般的に3つのレベルに分けられます。
このように区別することでモジュール化が実現し、各レベルを個別に、そして多くの場合独立して記述および処理することが可能になる。
まず、字句解析器は文字の線形シーケンスをトークンの線形シーケンスに変換します。これは「字句解析」または「字句解析」として知られています。[ 3 ]
第二に、パーサーはトークンの線形シーケンスを階層的な構文木に変換します。これは狭義には「構文解析」と呼ばれます。これにより、トークンの行がプログラミング言語の形式文法に準拠することが保証されます。構文解析段階自体は、2つの部分に分けられます。1つは構文木、または「具体的な構文木」で、これは文法によって決定されますが、一般的に実用には詳細すぎます。もう1つは抽象構文木(AST)で、これはこれを使いやすい形式に簡略化します。ASTと文脈分析のステップは、構文に意味と解釈を追加するため、意味解析の一形態と考えることができます。あるいは、形式的に記述または実装するのが困難または不便な構文規則の非公式な手動実装と考えることもできます。
第三に、文脈解析は名前を解決し、型をチェックします。このようなモジュール性は可能な場合もありますが、多くの実際の言語では、前のステップが後のステップに依存しています。例えば、C言語の字句解析器のハックは、トークン化が文脈に依存しているためです。このような場合でも、構文解析はしばしばこの理想的なモデルを近似するものと見なされます。
レベルは一般的にチョムスキー階層のレベルに対応しています。単語は、通常、正規表現で与えられるタイプ 3 文法である語彙文法で指定される正規言語です。句は、通常、バッカス・ナウア記法 (BNF) の生成規則で与えられるタイプ 2 文法である句構造文法で指定される、通常、決定論的文脈自由言語 (DCFL) である文脈自由言語( CFL ) です。句文法は、解析を容易にするために、完全な文脈自由文法よりもはるかに制約の多い文法で指定されることがよくあります。LRパーサーは任意の DCFL を線形時間で解析できますが、単純なLALR パーサーやさらに単純なLL パーサーはより効率的ですが、生成規則が制約されている文法しか解析できません。原則として、文脈構造は文脈依存文法によって記述でき、属性文法などの手段によって自動的に分析できますが、一般的にはこの手順は名前解決ルールと型チェックを介して手動で行われ、各スコープの名前と型を格納するシンボルテーブルによって実装されます。
正規表現で記述された字句仕様から字句解析器を、BNFで記述された句文法から構文解析器を自動的に生成するツールが作成されています。これにより、手続き型プログラミングや関数型プログラミングではなく、宣言型プログラミングを使用できます。注目すべき例として、lex - yacc のペアがあります。これらは具体的な構文木を自動的に生成しますが、構文解析器の作成者は、これを抽象構文木に変換する方法を記述するコードを手動で記述する必要があります。文脈解析も一般的に手動で実装されます。これらの自動ツールが存在するにもかかわらず、構文解析はさまざまな理由で手動で実装されることがよくあります。たとえば、句構造が文脈自由ではない場合、代替実装によってパフォーマンスやエラー報告が改善される場合、または文法をより簡単に変更できる場合などです。構文解析器は、Haskellなどの関数型プログラミング言語、 PythonやPerlなどのスクリプト言語、またはCやC++などの命令型プログラミング言語で記述されることがよくあります。

テキストベースのプログラミング言語の構文は通常、正規表現(字句構造用)とバッカス・ナウア記法(文法構造用のメタ言語)を組み合わせて、構文カテゴリ(非終端記号)と終端記号を帰納的に指定することによって定義されます。[ 4 ]構文カテゴリは、特定の構文カテゴリに属する値を指定するプロダクションと呼ばれる規則によって定義されます。 [ 1 ]終端記号は、構文的に有効なプログラムが構築される具体的な文字または文字列(例えば、 define、if、let、voidなどのキーワード)です。
構文は、文脈自由構文と文脈依存構文に分けられます。[ 4 ]文脈自由構文は、プログラミング言語のメタ言語によって指示される規則です。これらは、構文のその部分を取り囲む、または参照する文脈によって制約されませんが、文脈依存構文は制約されます。
言語には、同等の正規表現(語彙レベル)や、同じ言語を生成する異なる句規則など、複数の同等の文法が存在する可能性があります。LR文法のようなより広いカテゴリの文法を使用すると、LL文法のようなより限定的なカテゴリと比較して、より短く単純な文法で済む場合があります。LL文法では、より多くの規則を持つより長い文法が必要になる可能性があります。異なるが同等の句文法は、基となる言語(有効な文書の集合)は同じであっても、異なる構文解析木を生成します。
以下は、正規表現と拡張バッカス・ナウア記法を用いて定義された単純な文法です。これは、プログラミング言語Lispのデータ構文であるS式の構文を記述するもので、式、原子、数値、記号、リストという構文カテゴリの生成規則を定義します。
式=原子 |リスト原子 =数値|シンボル 数値 = [ + - ] ? [ '0' - '9' ] +シンボル = [ 'A' - 'Z' ][ 'A' - 'Z''0' - '9' ]. *リスト = '(' ,式* , ')'この文法は以下を規定する。
ここで、十進数、大文字と小文字、および括弧は終端記号です。
この文法における整形式トークンシーケンスの例は次のとおりです: ' 12345', ' ()', ' (A B C232 (1))'
プログラミング言語を指定するために必要な文法は、チョムスキー階層における位置によって分類できます。ほとんどのプログラミング言語の句文法はタイプ2文法を使用して指定できます。つまり、それらは文脈自由文法です[ 5 ] 。ただし、全体的な構文は文脈依存的であるため(変数宣言とネストされたスコープのため)、タイプ1です。しかし、例外もあり、一部の言語では句文法はタイプ0(チューリング完全)です。
Perl や Lisp のような言語では、言語の仕様 (または実装) により、構文解析フェーズ中に実行される構造が許可されています。さらに、これらの言語には、プログラマがパーサーの動作を変更できる構造があります。この組み合わせにより、構文解析と実行の区別が事実上曖昧になり、構文解析はこれらの言語では決定不能な問題となり、構文解析フェーズが完了しない可能性があります。たとえば、Perl ではBEGIN、ステートメントを使用して構文解析中にコードを実行することができ、Perl 関数プロトタイプは構文解釈を変更でき、残りのコードの構文的妥当性さえも変更できる可能性があります。[ 6 ] [ 7 ]口語的には、これは「Perl だけが Perl を解析できる」(構文解析中にコードを実行する必要があり、文法を変更できるため)または、より強くは「Perl でさえ Perl を解析できない」(決定不能であるため)と呼ばれます。同様に、構文によって導入されたLispマクロdefmacroも解析中に実行されるため、Lisp コンパイラには Lisp ランタイム システム全体が備わっている必要があります。対照的に、C マクロは単なる文字列置換であり、コードの実行は必要ありません。[ 8 ] [ 9 ]
言語の構文は有効なプログラムの形式を記述しますが、プログラムの意味や実行結果に関する情報は提供しません。記号の組み合わせに与えられる意味は、意味論(形式的か参照実装にハードコーディングされているかのいずれか)によって処理されます。意味論が意味を付与するには、有効な構文が確立されている必要があります。[ 4 ]構文的に正しいプログラムがすべて意味的に正しいとは限りません。構文的に正しいプログラムの多くは、言語の規則に従って不完全な形式であり、(言語仕様と実装の健全性によっては)翻訳または実行時にエラーが発生する可能性があります。場合によっては、そのようなプログラムは未定義の動作を示す可能性があります。プログラムが言語内で適切に定義されている場合でも、それを書いた人が意図していない意味を持つ可能性があります。
自然言語を例にとると、文法的に正しい文であっても意味を付与できない場合や、文自体が誤っている場合がある。
以下のC言語の断片は構文的には正しいが、意味的に定義されていない操作を実行している(はヌルポインタpであるため、操作および は意味を持たない)。p->realp->im
complex * p = NULL ; complex abs_p = sqrt ( p -> real * p -> real + p -> im * p -> im );より簡単な例として、
int x ; printf ( "%d" , x );は構文的には有効ですが、初期化されていない変数を使用しているため、意味的には定義されていません。一部のプログラミング言語(JavaやC#など)のコンパイラは、このような初期化されていない変数のエラーを検出しますが、これらは構文エラーではなく意味エラーとみなされるべきです。 [ 10 ] [ 11 ]