私は自分のJavaScriptベースのプログラミング言語を作成しています(そうです、それはクレイジーですが、それは学習のみを目的としています... たぶん?)。さて、私はパーサーについて読んでいて、最初のパスは次のようにコードソースをトークンに変換することです:
if(x > 5)
return true;
トークナイザー:
T_IF "if"
T_LPAREN "("
T_IDENTIFIER "x"
T_GT ">"
T_NUMBER "5"
T_RPAREN ")"
T_IDENTIFIER "return"
T_TRUE "true"
T_TERMINATOR ";"
私のロジックがしばらくの間それに対して正しいかどうかはわかりません。私のパーサーではそれはさらに良く(またはそうではない?)、それに変換されます(そう、多次元配列):
T_IF "if"
T_EXPRESSION ...
T_IDENTIFIER "x"
T_GT ">"
T_NUMBER "5"
T_CLOSURE ...
T_IDENTIFIER "return"
T_TRUE "true"
私はいくつかの疑問があります:
まあ、それはそれです。バイバイ!
一般に、トークナイザーの機能(lexerとも呼ばれます)をコンパイラーまたはインタープリターの他のステージから分離する必要があります。これの理由は基本的なモジュール性です。各パスは1種類のもの(文字など)を消費し、別のもの(トークンなど)を生成します。
キャラクターをトークンに変換しました。次に、トークンのフラットリストを意味のあるネストされた式に変換します。これは、従来parsingと呼ばれているものです。 JavaScriptのような言語の場合は、 再帰的降下解析 を調べる必要があります。優先レベルの異なる中置演算子を含む式を解析する場合、 Pratt parsing は非常に役立ち、特別な場合には通常の再帰的降下解析に頼ることができます。
ケースに基づいてより具体的な例を示すために、accept(token)
とexpect(token)
の2つの関数を記述できると想定します。これらは、ストリーム内の次のトークンをテストします作成した。言語の文法でステートメントまたは式のタイプごとに関数を作成します。たとえば、statement()
関数のPythonの疑似コードを次に示します。
def statement():
if accept("if"):
x = expression()
y = statement()
return IfStatement(x, y)
Elif accept("return"):
x = expression()
return ReturnStatement(x)
Elif accept("{")
xs = []
while True:
xs.append(statement())
if not accept(";"):
break
expect("}")
return Block(xs)
else:
error("Invalid statement!")
これにより、プログラムの抽象構文ツリー(AST)と呼ばれるものが得られ、操作(最適化と分析)、出力(コンパイル)、または実行(解釈)できます。
ほとんどのツールキットは、プロセス全体を2つのseparate部分に分割します
トークナイザーは、入力データをトークンに分割します。パーサーはトークン「ストリーム」のみを操作して、構造を構築します。
あなたの質問はトークナイザーに焦点を当てているようです。しかし、2番目のソリューションでは、文法パーサーとトークナイザーを1つのステップに混合します。理論的にはこれも可能ですが、初心者にとっては、他のほとんどのツールやフレームワークと同じ方法ではるかに簡単です。手順を分けてください。
あなたの最初の解決策:私はあなたの例をこのようにトークン化します:
T_KEYWORD_IF "if"
T_LPAREN "("
T_IDENTIFIER "x"
T_GT ">"
T_LITARAL "5"
T_RPAREN ")"
T_KEYWORD_RET "return"
T_KEYWORD_TRUE "true"
T_TERMINATOR ";"
ほとんどの言語では、keywordsをメソッド名、変数名などとして使用できません。これはすでにトークナイザーレベル(T_KEYWORD_IF
、T_KEYWORD_RET
、T_KEYWORD_TRUE
)。
次のレベルはこのストリームを受け取り、正式な文法を適用することにより、次のようなデータ構造(AST-抽象構文ツリーと呼ばれることが多い)を構築します。
IfStatement:
Expression:
BinaryOperator:
Operator: T_GT
LeftOperand:
IdentifierExpression:
"x"
RightOperand:
LiteralExpression
5
IfBlock
ReturnStatement
ReturnExpression
LiteralExpression
"true"
ElseBlock (empty)
手でパーサーを実装することは、通常、いくつかのフレームワークによって行われます。そのようなものを手作業で実装することおよびは、通常、大学の学期の後半に効率的に行われます。したがって、実際には何らかのフレームワークを使用する必要があります。
文法パーサーフレームワークの入力は通常、ある種の [〜#〜] bnf [〜#〜] の形式文法です。あなたの "if"部分は次のようになります:
IfStatement: T_KEYWORD_IF T_LPAREN Expression T_RPAREN Statement ;
Expression: LiteralExpression | BinaryExpression | IdentifierExpression | ... ;
BinaryExpression: LeftOperand BinaryOperator RightOperand;
....
それはアイデアを得るためだけです。 JavaScriptのような実際の言語を正しく解析するのは簡単な作業ではありません。でも面白い。
元の方法よりも私の方法は良いですか悪いですか?私のコードは常に解釈されるのではなく、読み込まれてコンパイル(PHPなどの別の言語に翻訳)されることに注意してください。
元の方法は何ですか?言語を実装するにはさまざまな方法があります。あなたの言語は実際には問題ないと思いますが、C#に翻訳する言語 hackプログラミング言語 を自分で構築しようとしたことがあります。多くの言語コンパイラは中間言語に変換されますが、それは非常に一般的です。
トークナイザーの後、正確に何をする必要がありますか?私はこのパスで本当に迷っています!
トークン化後、それをparseする必要があります。 Boost.Spirit 、Cocoなど、優れたレクサー/パーサーフレームワークを使用します。それらの数百があります。または、独自のレクサーを実装できますが、これには時間とリソースがかかります。コードを解析するには多くの方法がありますが、私は一般的に 再帰的降下解析 に依存しています。
次に、コード生成を行う必要があります。それが私の意見では最も難しい部分です。そのためのツールもありますが、必要に応じて手動で実行できます。プロジェクトで実行しようとしましたが、かなり基本的でバグが多く、役立つコードがいくつかあります here および- ここ 。
私がそれを行う方法を学ぶための良いチュートリアルがありますか?
先に提案したように、toolsを使用して実行します。文書化された非常に優れたパーサーフレームワークがたくさんあります。詳細については、このことについて知っている人に尋ねてみてください。 @DeadMGは ラウンジC++ で "Wide"と呼ばれるプログラミング言語を構築しています。彼に相談してみてください。
私がこのステートメントをプログラミング言語で持っているとしましょう:
if (0 < 1) then
print("Hello")
レクサーはそれを次のように変換します。
keyword: if
num: 0
op: <
num: 1
keyword: then
keyword: print
string: "Hello"
次に、パーサーは情報(別名「トークンストリーム」)を取得してこれを作成します。
if:
expression:
<:
0, 1
then:
print:
"Hello"
これが役立つかどうかはわかりませんが、役立つことを願っています。