「やさしい コンパイラの作り方 入門」 のコンパイラをちょっと直した
以下のコンパイラ自作本を本に、trifleという小さな言語のコンパイラを半自作しました。
やさしいコンパイラの作り方入門
私のGitHubにそのソースコードをあげています。
github.com
簡単に説明すると、
trifleという仮想の小さな言語を想定し、それをコンパイルするようなコンパイラをC++で書いています。
trifleはコンパイルされるとニーモニックを生成し、そのニーモニックを実行する仮想環境をC++で構築して実行しています。
この仮想環境が少し特殊で、スタック構造の記憶階層のあるチューリングマシンのような動きをします。(多分)
ここで、バグっぽいものに遭遇したのでメモ程度に書いておきます。
(本当にすごい先人達が作ったサンプルのはずなので、僕の間違いの可能性が高いですが、同じような状況に陥っている人がもしいるとしたらその人のためにと思って)
compile.cppの中の405行目、
statementの中でLBrace('{'のこと)を見つけ、その中でさらにstatementを見つけた後、Semicolonを見つけるとbreakしてしまっているんです。意味的に考えてもここはbreakではなくcontinueなのではないかなぁと思います。Semicolonを見つけてもwhileループは出るべきではなく、RBrace('}'のこと)を見つけた時初めてループをstatementを出るのが良いではないのかぁ、と思ったり。
実際、サンプルコードはセミコロンが付いていたり付いていなかったりしていたので、行の終わりとRBraceの後には必ずSemicolonをつけるような言語設計にすれば、全てのサンプル実装で正しくコンパイルできます。
RubyでHTTPサーバを立てる②
昨日の投稿(https://be-kan.hatenablog.jp/entry/2018/05/24/012445)の続きになります。
元記事はこちら
blog.appsignal.com
内容的には
・HTTPサーバの上でRails Appを動かす
のみです。
当内容、GitHubにソースコードをあげています。
https://github.com/be-kan/http_server_in_ruby
HTTPサーバの上でRaila Appを動かす
使用するRails Appはこちらになります。
https://github.com/jeffkreeftmeijer/wups.git
ただデータをpostしその一覧を取得できるだけのものです。
もちろん自分で用意していただいても構いません。
http_server.rbがあるディレクトリで、Rails Appをgit submoduleで管理します。
$ ls http_server.rb $ git submodule add https://github.com/jeffkreeftmeijer/wups.git sample_blog
前回、http_server.rbは以下のコードで終了していると思います。
require 'socket' require 'rack' require 'rack/lobster' app = Rack::Lobster.new server = TCPServer.new 5678 while session = server.accept request = session.gets puts request method, full_path = request.split(' ') path, query = full_path.split('?') status, headers, body = app.call({ 'REQUEST_METHOD' => method, 'PATH_INFO' => path, 'QUERY_STRING' => query }) session.print "HTTP/1.1 #{status}\r\n" headers.each do |key, value| session.print "#{key}: #{value}\r\n" end session.print "\r\n" body.each do |part| session.print part end session.close end
Rack::Lobster Appを動かしていましたが、これをRails Appに変更します。
require 'socket' require_relative 'sample_blog/config/environment' app = Rails.application server = TCPServer.new 5678
これでサーバを立ててアクセスしてみると、以下のようなエラーがきます。
500 Internal Server Error If you are the administrator of this website, then please read this web application's log file and/or the web server's log file to find out what went wrong.
言われた通りにlogを確認すると、
Error during failsafe response: Missing rack.input
とのことです。
Rack Appを動かすには、REQUEST_METHOD, PATH_INFO, QUERY_STRINGだけで十分だったのですが、Rails Appを動かすにはそれだけでは不十分のようです。
Rackには、Rack::Lintという、RackがRailsに渡すべき変数を用意してくれるものがあるので、
Rails.applicationをRack::Lintでラッピングします。
require 'socket' require_relative 'sample_blog/config/environment' app = Rack::Lint.new(Rails.application) server = TCPServer.new 5678
これでもう一度サーバを立ててlocalhost:5678にアクセスすると、サーバがクラッシュしました。
~path/to/rack/lint.rb:20:in `assert': env missing required key SERVER_NAME (Rack::Lint::LintError)
Rack::LintはRailsに十分に変数を渡しますが、そもそもRack::Lintに必要な変数を渡せていないようです。
エラーで表示された変数を追加して行くと、最終的に以下のようになります。
# ... method, full_path = request.split(' ') path, query = full_path.split('?') input = StringIO.new input.set_encoding 'ASCII-8BIT' status, headers, body = app.call({ 'REQUEST_METHOD' => method, 'PATH_INFO' => path, 'QUERY_STRING' => query || '', 'SERVER_NAME' => 'localhost', 'SERVER_PORT' => '5678', 'rack.version' => [1,3], 'rack.input' => input, 'rack.errors' => $stderr, 'rack.multithread' => false, 'rack.multiprocess' => false, 'rack.run_once' => false, 'rack.url_scheme' => 'http' })
すると、以下のようなメッセージがブラウザに表示されます。
エラーとはいえ、ついにRails側のエラーが来ましたね!
ログを確認すると、
IPAddr::InvalidAddressError: invalid address
というエラーです。
クライアントのIPアドレスを指定しなければなりません。
今はlocalhostで動かしているので、以下のように設定します。
status, headers, body = app.call({ # ... 'SERVER_PORT' => '5678', 'REMOTE_ADDR' => '127.0.0.1', 'rack.version' => [1,3], # ... })
ついにRailsが表示されました。
ここから、Rails Appに対して、POSTを行います。
Rails App的にはすでにpostに対応しているので、あとはサーバとのつなぎこみのみです。
ちなみに、元記事にはありませんが、Rails Appのデータベースがないので、
rake db:migrate
でデータベースを作成しておきましょう。
サーバを立て、http://localhost:5678/postsにアクセスし、New Postから新規にpostを作成しようとすると
ActionController::InvalidAuthenticityToken
と言われます。
通常ならauthenticity tokenはリクエストと共に送信されるのですが、今のコードでは何もポストデータを送っていないようです。
そこで、session.getsで取得したリクエストの情報を、headerにハッシュとして受け取ります。
リクエストの大きさ(何バイトか)をあらかじめ知るためにbodyにContent-Lengthの情報を入れ、さらにrack.inputには先のbodyで初期化したString.IOインスタンスを入れます。
headerのクッキーも指定し、authenticityをクリアします。
headers = {} while (line = session.gets) != "\r\n" key, value = line.split(':', 2) headers[key] = value.strip end body = session.read(headers["Content-Length"].to_i) # ... status, headers, body = app.call({ # ... 'REMOTE_ADDR' => '127.0.0.1', 'HTTP_COOKIE' => headers['Cookie'], 'rack.version' => [1,3], 'rack.input' => StringIO.new(body), 'rack.errors' => $stderr, # ... })
これで無事POSTもできるようになりました!
RubyでHTTPサーバを立てる①
主にこちらの内容の翻訳になります
また、この投稿の続きを書きました。
RubyでHTTPサーバを立てる② - be-kan's blog
内容的には、以下の4つです。
・TCPサーバを立てる
・TCPクライアントを作成する
・HTTPサーバを立てる
・HTTPサーバの中でRack Appを動かす
当内容、GitHubにソースコードをあげています。
https://github.com/be-kan/http_server_in_ruby/blob/master/README.md
TCPサーバを立てる
require 'socket' server = TCPServer.new(5678) while session = server.accept session.puts "hello time is #{Time.now}" session.close end
上記のコードでは、ポート5678番を開けて、クライアントから接続されるのを待っています。
クライアントから接続があると、クライアントにメッセージを送り、接続を切って次の接続を待つ、というようなコードになっています。
TCPクライアントを作成する
equire 'socket' server = TCPSocket.new 'localhost', 5678 while line = server.gets puts line end server.close
上記のコードでは、ポート5678番に接続するクライアントを作成しています。
サーバに接続すると、先ほどのコードによりクライアントに「hello~」というメッセージが送信されており(つまりクライアントはサーバからそのメッセージを受け取り)、それを表示し、サーバとの接続を切ります。
試しにやってみましょう。
$ ruby tcp_server.rb
でサーバを立てた後、
$ ruby tcp_client.rb
とすると、メッセージが返って来るはずです!
HTTPサーバを立てる。
require 'socket' server = TCPServer.new 5678 while session = server.accept request = session.gets puts request session.print "HTTP/1.1 200\r\n" session.print "Content-Type: text/html\r\n" session.print "\r\n" session.print "Hello world! The time is #{Time.now}" session.close end
HTTPサーバと言っても、TCPサーバとほとんど変わりません。
異なるのは、HTTPサーバではHTTPプロトコルでメッセージをやり取りする、というところです。
上記のコードでは、そのプロトコル(通信の約束事・フォーマットのようなもの)を手動で書いています。
HTTPサーバの中でRack Appを動かす
require 'socket' require 'rack' require 'rack/lobster' app = Rack::Lobster.new server = TCPServer.new 5678 while session = server.accept request = session.gets puts request method, full_path = request.split(' ') path, query = full_path.split('?') status, headers, body = app.call({ 'REQUEST_METHOD' => method, 'PATH_INFO' => path, 'QUERY_STRING' => query }) session.print "HTTP/1.1 #{status}\r\n" headers.each do |key, value| session.print "#{key}: #{value}\r\n" end session.print "\r\n" body.each do |part| session.print part end session.close end
このサーバをブラウザで開くと、以下のような画面になるはずです。
「flip!」をクリックすると、TCPサーバにリクエストが送られます。TCPサーバは、送られてきたリクエストをmethod, path, queryに分解します。
そして分解したmethodなどを、Rack Appに送り、Rack Appからstatus, headers, bodyを受け取ります。
実際に送られたパラメータの値は以下です。
{ 'REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/', 'QUERY_STRING' => '?flip=left' }
そして、TCPサーバは、Rackから受け取った情報を、クライアント側に送っています。
次回は、HTTPサーバの中でRailsを動かしてみたいと思います!
Hello Hatena Blog
ブログ始めました。
現在東京大学工学部3年です。
1年前にエンジニアに興味を持ち、半年ちょっと前から本格的に勉強し始めました。
このブログでは、日々の勉強をそれなりの粒度でアウトプットしていきたいと思います。
(細かなTipsはQiitaに、ってスミワケしようかな)
今は(主にインターンで)Railsでバックエンド開発をやっていますが、インフラや低レイヤーまで幅広くやっていきたいです。
今のところ大学院に進学するつもりで、院では機械学習をやりたくて今統計やデータサイエンスも勉強しています。
勉強すればするほど「世の中にはすごいエンジニアがいるなー。自分クソ雑魚やん」と感じるような、1歩進むごとに3歩先が見えて絶望するような世界ですが、焦らずコツコツやっていきます。