2年生の後に教育が終了し、語彙が制限され、スピーチが不明瞭な場合、単純に愚かである場合、これらのあいまいなラテン文字を知らないが、プログラマーになりたい場合は、牛語Yobaが役立ちます。 ヨーバは本物の男の子のための言語です!
しかし、真剣に、ある日、仕事で誰かが冗談でgop言語を書くことを提案して、だれでもプログラマーのように感じることができるようにしました。 「cho」という単語とそのすべてで構築を開始します。 ここで、コンピュータサイエンスの分野で私の人生の道のりの教育に出会っていないので、コンパイラビルディング、正式な文法、および2年目または3年目に普通の学生が食べるその他の興味深いコースすべてを見逃したことに注意してください。 コンパイラの構築に関するWirthの本は、BNFなどのあらゆる種類の巧妙な用語の知識を追加しましたが、実際的な利点はありませんでした-私は単一のコンパイラを書いたことがありません。 したがって、このタスクは私にとって非常に興味深いものでした。
あなたが18歳以上の場合、私たちの母国語のわいせつな語彙を適切に認識しており、どこから始めたいかに興味があるなら、猫にようこそ。
文法を操作するためのすべてのツールの中で、私はほんの少しだけ言及しました。 まず第一に、これらはもちろんlex、flex、yacc、bison、antlrであり、主にC / C ++コンパイラーに使用されます。 第二に、それはocamllexとocamlyaccです。これらは、ご想像のとおり、okamlaでコンパイラーを開発するために設計されています。 そして第三に、それはfslexxとfsyaccです-F#言語についてのみ同じことです。これはocamlから完全に少しコピーされますが、その後かなり多くのグッズで生い茂ります。 F#私は自分のシステムに欠席しているとして却下しました。 はい、時には幅広い選択が悪いこともあります。これらすべてのレクサーとパーサーで混乱するのを恐れていました。
ちなみに、これは私にとってかなり奇妙で不快な発見でした。言語の分析は、異なるツールで実行される2つの別々の段階で構成されていることがわかりました。 しかし、悲しいかな。
言語の種類
嘘をつくことはありません。すぐに言語のアーキテクチャ全体を考えたわけではなく、徐々に機能を徐々に仕上げていきました。 しかし、素材のサイズを小さくするために(そして、6つの美しい日曜日の朝の時間に私を連れて行った道に素早く行くために)、すべてが事前に計画されたふりをします。 私たちの言語の各命令は、例えば、オブジェクトです。 C ++では、特定の命令基本クラスの子孫の1つとして、複雑な構造として、または一般的にはユニオンとして表現できます。 幸いなことに、okamlを使用すると、できるだけ簡単にすべてを実行できます。 最初のファイル、yobaType.mlは、考えられるすべてのタイプの指示を記述しており、可能な限りシンプルに設計されています。
type action =
DoNothing
| AddFunction of string * action list
| CallFunction of string
| Stats
| Create of string
| Conditional of int * string * action * action
| Decrement of int * string
| Increment of int * string;;
各言語構成体は、これらのタイプのいずれかにキャストされます。 DoNothingは単なるNOPステートメントであり、まったく何もしません。 Createは変数を作成します(これらは常に整数です)。DecrementとIncrementはそれぞれ、指定された変数をある数だけ増減します。 さらに、作成されたすべての変数と条件付きの統計情報を表示する統計があります。if実装では、特定の変数に必要な値(または大きな値)が含まれているかどうかを確認できます。 最後に、AddFunctionとCallFunctionを追加しました。これは、実際には非常にプロシージャである独自の関数を作成して呼び出す機能です。
言語の文法
次のステップは、文法、つまりある種の指示に変わる言語の構成を記述することです。
コードを表示する前に、私たちの言語がどのように機能するかを説明します。 統計のクエリ(これはサービスチームのようなものです)と関数の作成を除くすべてのデザインは、キーワードで始まり、キーワードで終わります。 これにより、必要に応じて安全に改行とインデントを配置できます。 これに加えて(ロシア語で作業しています)、変数と値の両方を渡す必要がある場合のために、いくつかの手順を具体的に作成しました。 なぜこれが必要だったのかは後でわかります。 そのため、yobaParser.mlyファイル:
%{
open YobaType
%}
%token <string> ID
%token <int> INT
%token RULEZ
%token GIVE TAKE
%token WASSUP DAMN
%token CONTAINS THEN ELSE
%token FUCKOFF
%token STATS
%token MEMORIZE IS
%token CALL
%start main
%type <YobaType.action> main
%%
main:
expr { $1 }
expr:
fullcommand { $1 }
| MEMORIZE ID IS fullcommandlist DAMN { AddFunction($2, $4) }
fullcommandlist:
fullcommand { $1 :: [] }
| fullcommand fullcommandlist { $1 :: $2 }
fullcommand:
WASSUP command DAMN { $2 }
| STATS { Stats }
command:
FUCKOFF { DoNothing }
| GIVE ID INT { Increment($3, $2) }
| GIVE INT ID { Increment($2, $3) }
| TAKE ID INT { Decrement($3, $2) }
| TAKE INT ID { Decrement($2, $3) }
| RULEZ ID { Create($2) }
| CALL ID { CallFunction($2) }
| CONTAINS ID INT THEN command ELSE command { Conditional($3, $2, $5, $7) }
| CONTAINS INT ID THEN command ELSE command { Conditional($2, $3, $5, $7) }
%%
最初に行うことは、見出しを挿入することです。最初に説明したアクションタイプを含むYobaTypeモジュールを開きます。 言語(変数)のキーワードではない数字と文字列の場合、2つの特別な型を宣言します。これらは、それらが何を含むかを正確に示します。 %トークンディレクティブを使用するキーワードごとに、文法でこの単語を識別する独自のタイプも作成します。 それらのすべてを少なくとも1行で示すことができます。そのようなレコードは、命令のタイプに従ってすべてをグループ化します。 作成したすべてのトークンは、文法パーサーが実行すべき内容を決定する正確な置換タイプであることに注意してください。 それらはあなたが好きなもの、それらが言語自体でどのように見えるか、と呼ぶことができます。これについては後で説明します。 文法のエントリポイントがメインであり、アクションタイプのオブジェクトは常にそれを返す必要があること、つまり、インタプリタへの指示であることを示します。 最後に、2つの%%記号の後、文法自体について説明します。
- 命令は、コマンド(フルコマンド)または関数の作成で構成されます。
- この関数は、コマンドのリスト(fullcommandlist)で構成されています。
- コマンドは、サービス(STATS)または通常(コマンド)のいずれかです。この場合、キーワードでラップする必要があります。
- 普通のチームでは、すべてがシンプルで、ペイントすらしません。
中かっこでは、文字列がこのオプションに一致した場合の処理を示します。$ Nは、構造のN番目のメンバーを示します。 たとえば、「CALL ID」(IDは文字列です。忘れないでください)を満たす場合、CallFunction命令を作成し、パラメーター$ 2(IDのみ)-呼び出される関数の名前として渡します。
レクサー-言語をキーワードに変える
同時に、最も単純で最も退屈な部分に到達しました。 言語の単語からトークンへの変換を記述するだけなので、簡単です。 面倒なのは、字句解析器(またはオカムロフスキー字句解析器のみ)がロシア語で動作するように十分に設計されていないため、文字列と同様にロシア語の文字でしか動作できないためです。 言語のキーワードの大文字と小文字を区別しないようにしたかったので、これはhemoの束を追加しました-「与える」だけを書くのではなく、文字ごとにスペルオプションをペイントする必要がありました。 一般的には、yobaLexer.mllファイルをご覧ください。
- {
- YobaParserを開く
- 例外 eof
- }
- ルールトークン=解析
- ( "and" | "And" ) ( "d" | "D" ) ( "and" | "And" ) ( ' ' ) +
- ( "n" | "H" ) ( "a" | "A" ) ( "x" | "X" ) ( "y" | "Y" ) ( "y" | "Y" ) { FUCKOFF }
- | ( "b" | "B" ) ( "a" | "A" ) ( "l" | "L" ) ( "a" | "A" )
- ( "n" | "H" ) ( "c" | "C" ) ( ' ' ) +
- ( "n" | "H" ) ( "a" | "A" ) ( "x" | "X" ) {統計}
- | [ ' ' ' \ t ' ' \ n ' ' \ r ' ] {トークンlexbuf }
- | [ ' 0 '-' 9 ' ] + { INT ( int_of_string ( Lexing。Lexeme lexbuf ) ) }
- | ( "d" | "D" ) ( "a" | "A" ) ( "y" | "Y" ) {与える}
- | ( "n" | "H" ) ( "a" | "A" ) {テイク}
- | ( "h" | "h" ) ( "o" | "o" ) { WASSUP }
- | ( "th" | "th" ) ( "o" | "O" ) ( "b" | "B" ) ( "a" | "A" ) {くそ}
- | ( "l" | "l" ) ( "y" | "y" ) ( "b" | "b" ) ( "l" | " l " ) ( "y" | " y " ) { RULEZ }
- | ( "e" | "E" ) ( "c" | "C" ) ( "t" | "T" ) ( "b" | "b" ) {含む }
- | ( "t" | "T" ) ( "a" | "A" ) ( "d" | "D" ) ( "a" | "A" ) { THEN }
- | ( "and" | "AND" ) ( "l" | "L" ) ( "and" | "AND" ) {その他 }
- | ( "y" | "y" ) ( "c" | "c" ) ( "e" | "e" ) ( "k" | "k" ) ( "and" | "and" ) { MEMORIZE }
- | ( "e" | "E" ) ( "t" | "T" ) ( "o" | "O" ) { IS }
- | ( "x" | "X" ) ( "y" | "Y" ) ( "y" | "Y" ) ( "n" | "H" ) ( "and" | "AND" ) { CALL }
- |
- ( "a" | "b" | "c" | "g" | "d" | "e" | "e" | "w"
- | 「z」 | 「そして」 | 「th」 | 「〜」 | 「l」 | 「m」 | 「n」 | 「お」
- | 「p」 | 「p」 | 「s」 | 「t」 | 「u」 | 「f」 | x | 「c」
- | 「h」 | 「w」 | 「u」 | 「b」 | 「s」 | 「b」 | 「e」 | 「u」 | "I" ) + { ID ( Lexing。lexeme lexbuf ) }
- | eof { Eofを上げる }
わいせつな言語の語彙はすべてここで説明しているため、このコードは子供や精神組織が脆弱な人には見せてはならないことに注意してください。 さらに、最初にパーサーのモジュールを開きます。このモジュールでは、すべてのタイプ(STATS、GIVE、TAKEなど)が定義されており、入力が終了したときにスローされる例外を作成します。 11行目に注意してください。スペースやその他のゴミは無視する必要があることを示していますが、スペースを含む命令の後には特に続きます。 12行目と25-28行目は変数の数と名前を処理し、29行目はファイルの終わりを示す例外をスローします。
通訳
最後の部分-言語構成を処理するインタープリター自体。 最初にコード、次に説明:
- YobaTypeを開く
- let identifiers = Hashtbl 。 作成 10 ;;
- let funcs = Hashtbl 。 作成 10 ;;
- let print_stats ( ) =
- let print_item id amount =
- Printf 。 printf ">> Yo!You have%s:%d" id amount ;
- print_newline ( ) ;
- 標準出力 を フラッシュ
- Hashtbl 。 iter print_item identifiers ;;
- arithm id op値( ) =
- 試してみる
- Hashtbl 。 識別子id ( op ( Hashtbl 。識別子idの検索 )値)を 置き換えます。
- Printf 。 printf ">>くそ質問\ n" ; フラッシュ 標準出力
- Not_found- > Printfで。 printf ">> X @#on、you%sは嫌いです \ n" id ; 標準出力を フラッシュします ;;
- let cond cond id id act1 act2 ( ) =
- 試してみる
- hashtblの場合 。 識別子を見つける id > = amount then process_action act1 ( ) else process_action act2 ( )
- Not_foundで->
- Printf 。 printf ">>なに?!\ n" ;
- フラッシュ 標準出力
- および process_action = function
- | Create ( id ) -> ( function ( ) -> Hashtbl。ID 0を 追加 )
- | 減少( amount、id ) -> arithm id ( - ) amount
- | インクリメント( amount、id ) -> arithm id ( + ) amount
- | 条件付き( amount、id、act1、act2 ) -> cond amount id act1 act2
- | DoNothing -> ( 関数 ( ) -> ( ) )
- | 統計-> print_stats
- | AddFunction ( id、funclist ) -> ( function ( ) -> Hashtbl。funcs id funclistを追加 )
- | CallFunction ( id ) -> callfun id
- および callfun id ( ) =
- let f : YobaType 。 アクションリスト= Hashtbl funcs id を 見つける
- 一覧 iter ( 関数 x- > process_action x ( ) ) f
- ;;
- 本当 ながら
- 試してみる
- let lexbuf = Lexing 。 from_channel stdin in
- process_action ( YobaParser。main YobaLexer。token lexbuf ) ( )
- と
- YobaLexer 。 Eof- >
- print_stats ( ) ;
- 出口0
- | 解析中。 Parse_error- >
- Printf 。 printf ">>どちらでもない@#理解できなかったb @#!\ n" ;
- フラッシュ 標準出力
- | 失敗( _ ) ->
- Printf 。 printf ">>どちらでもない@#理解できなかったb @#!\ n" ;
- フラッシュ 標準出力
- やった
まず、変数用と関数用の2つのハッシュテーブルを作成します。 初期サイズの10は懐中電灯から取得され、同じトレーニング言語を使用していますが、一度に多くの機能が必要なのはなぜですか。
次に、2つの小さな関数を宣言します。1つは統計の出力用、もう1つは変数のインクリメント/デクリメント用です。
次に、3つの関数のグループが一度に来ます。condは条件付きコンストラクト(私たちのif)を処理し、callfunは関数の呼び出しを担当し、process_actionは入力として来た命令を処理します。 3つの機能すべてが互いに依存している理由を説明します。
process_actionのすべてのオプションはアクションを実行せず、単に実行する関数を返すだけであることに注意してください。 最初はそうではありませんでしたが、この小さな変更により、ユーザー関数のサポートを言語に簡単かつ簡単に追加できました。
最後に、コードの最後の部分は、ループ内で青に変わる前に、パーサーの結果を読み取って処理します。
これにMakefileを追加すると、再生できます:
all:
ocamlc -c yobaType.ml
ocamllex yobaLexer.mll
ocamlyacc yobaParser.mly
ocamlc -c yobaParser.mli
ocamlc -c yobaLexer.ml
ocamlc -c yobaParser.ml
ocamlc -c yoba.ml
ocamlc -o yoba yobaLexer.cmo yobaParser.cmo yoba.cmo
clean:
rm -f *.cmo *.cmi *.mli yoba yobaLexer.ml yobaParser.ml
言語チェック
さて、注意、なぜ値と変数の異なる順序をサポートしたのですか? 事実は、言語が非常に、つまり自然で、通訳が文字通りあなたに話しかけているということです。 したがって、私自身は何を書くべきかについて常に混乱しています。
$ ./yoba
3
4
2
@#
>>
>>
>>
>> ! : 7
>> ! : -2
4 1 @#
>>
>> ! : 7
>> ! : -1
4 @# @#
>>
>>
>>
>> ! : 14
>> ! : -3
欠点の1つは、関数を呼び出すときに、操作の数によるインクリメント/デクリメントの成功に関するコメントが出てくることです。
面白かったと思います。 すべてのインタープリターコードは
ここからダウンロードでき
ます(2kb) 。
より経験豊富な人々がコードについてのコメントを共有する場合、私は感謝します。