Ruby Internal - Code Ruby của bạn được thực thi như thế nào (Phần 1)
hardcore
17
Ruby
114
compiler
9
White

Cẩm Huỳnh viết ngày 30/08/2016

Bài viết nằm trong chuỗi hard-core là một group học nhóm lập ra bởi một số thành viên của ruby VN. Rule của nhóm nằm tại đây Idea là mỗi week thành viên sẽ pick ra một topic và sau 1 tuần sẽ phải có output về topic đó.

Lời nói đầu

Các ví dụ trong chuỗi bài viết này chủ yếu được lấy từ cuốn "Ruby Under a Microscope" của tác giả Pat Shaughnessy

Là một Rubyist, đã bao giờ bạn tự hỏi bản thân mình rằng, một đoạn Ruby code như bên dưới được thực thi như thế nào không?

puts 2 + 3
# => 5

Rằng đoạn code trên được Ruby đọc và chuyển hóa bao nhiêu lần trước khi nó được thực thi?

Chính xác là ba lần. Dù bạn chạy một đoạn code siêu đơn giản như trên, một Rails app vĩ đại hay một rake task, code của bạn đều được Ruby tách thành những phần nhỏ (tí hon) và chuyển hóa thành những format khác nhau tổng cộng ba lần. Khoảng thời gian từ khi bạn gõ lệnh ruby -e "puts 2 + 3" đến khi bạn thấy số 5 được hiện ra trên console, đó thật sự là quá trình dài mà rất nhiều kĩ thuật, công nghệ và thuật toán được dùng đến.

alt text

  1. Đầu tiên Ruby tokenize đoạn code của bạn thành những token.
  2. Tiếp theo nó parse những token đó thành Abstract Syntax Tree (AST) Node
  3. Sau đó compile (biên dịch) thành bytecodes là một tập các lệnh thực thi cấp thấp (tuy nhiên không phải là mã máy). Đây chính là tập lệnh được chạy trong máy áo Ruby (Ruby Virtual Machine).

Tokenizing

Thuật toán tokenize

Ở chương này mình sẽ nói kĩ về phần tokenizing.

Giả sử bạn có một đoạn Ruby code sau

# simple.rb
10.times do |n|
  puts n
end
$ ruby simple.rb

Đầu tiên Ruby sẽ đọc file simple.rb và split nó thành những kí tự.

[1] [0] [.] [t] [i] [m] [e] [s] [ ] [d] [o] [|] [n] [|]

Ta có một con trỏ đọc từng kí tự của hàng đầu tiên.
Ruby nhận ra rằng số 1 là bắt đầu của một số, nó sẽ tiếp tục đọc cho đến khi trỏ đến một kí tự không phải số.

 *
[1] [0] [.] [t] [i] [m] [e] [s] [ ] [d] [o] [|] [n] [|]

Tiếp theo con trỏ đến số 0, vẫn là một con số, nên nó nhảy đến kí tự tiếp theo

     *
[1] [0] [.] [t] [i] [m] [e] [s] [ ] [d] [o] [|] [n] [|]

Ruby nhận ra rằng . vẫn có thể là một phần của số thực và nhảy đến kí tự tiếp theo.

         *
[1] [0] [.] [t] [i] [m] [e] [s] [ ] [d] [o] [|] [n] [|]

Đến khi gặp kí tự t, đây không phải là một phần của một con số, đồng thời kết luận không còn con số nào sau dấu . lúc nãy nữa, Ruby nhận thấy rằng dấu . đó là thể là một phần của token khác, nên trỏ ngược về thêm một kí tự.

             *
[1] [0] [.] [t] [i] [m] [e] [s] [ ] [d] [o] [|] [n] [|]
         *
[1] [0] [.] [t] [i] [m] [e] [s] [ ] [d] [o] [|] [n] [|]

Lúc này Ruby sẽ convert những kí tự số nó đã đi qua thành (tINTERGER) token.

            *
(tINTEGER) [.] [t] [i] [m] [e] [s] [ ] [d] [o] [|] [n] [|]

Ruby trỏ tiếp đến kí tự tiếp theo và convert kí tự nó vừa đi qua dấu . thành token (.)

                *
(tINTEGER) (.) [t] [i] [m] [e] [s] [ ] [d] [o] [|] [n] [|]

Và như thế Ruby lướt qua times, và convert nó thành token (tIDENTIFIER)

                              *
(tINTEGER) (.) (tIDENTIFIER) [d] [o] [|] [n] [|]

Note: IDENTIFER không phải là reserved keyword (từ khóa) trong Ruby, nó dùng để chỉ một biến (var), một hàm (function), hay một method.

Tiếp theo Ruby đọc qua do và nhận ra đây là reserved keyword (keyword_do)

                                           *
(tINTEGER) (.) (tIDENTIFIER) (keyword_do) [|] [n] [|]

Và rồi, Ruby cũng token xong hàng thứ nhất của đoạn code Ruby của chúng ta.

                                           *
(tINTEGER) (.) (tIDENTIFIER) (keyword_do) (|) (tIDENTIFIER) (|)

Ripper

Chúng ta đã hiểu ý tưởng cơ bản của việc tokenize. Để vọc thêm về Ruby tokenizer, ta có thể dùng tool Ripper để kiểm tra toàn bộ token được sinh ra của một đoạn Ruby code.

require 'ripper'
require 'pp'
code = <<STR
10.times do |n|
 puts n
end
STR
puts code
pp Ripper.lex(code)
/*
[[[1, 0], :on_int, "10"],
[[1, 2], :on_period, "."],
[[1, 3], :on_ident, "times"],
[[1, 8], :on_sp, " "],
[[1, 9], :on_kw, "do"],
[[1, 11], :on_sp, " "],
[[1, 12], :on_op, "|"],
[[1, 13], :on_ident, "n"],
[[1, 14], :on_op, "|"],
[[1, 15], :on_ignored_nl, "\n"],
[[2, 0], :on_sp, " "],
[[2, 2], :on_ident, "puts"],
[[2, 6], :on_sp, " "],
[[2, 7], :on_ident, "n"],
[[2, 8], :on_nl, "\n"],
[[3, 0], :on_kw, "end"],
[[3, 3], :on_nl, "\n"]]
*/

Như ta thấy, các token được sinh ra ở đoạn code trên là:

  • 10 = INT
  • times, n = ident
  • do, end = keyword

Tổng kết

Ta đã hoàn thành phần 1 - Tokenizer trong quá trình thực thi của code Ruby. Ở phần tiếp theo mình sẽ nói về phần Parsing, quá trình mà Ruby sử những token được sinh ra trong phần Tokenizer này, gộp nó lại thành những cú pháp có ý nghĩa với Ruby.

Tham khảo:

Ruby Under a Microscope - Pat Shaughnessy

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

Cẩm Huỳnh

40 bài viết.
342 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
39 9
(Ảnh) Vì sao lại là Bật Đèn? Ai từng đọc qua Tắt Đèn hẳn đã biết tác phẩm được kết thúc bằng tình huống: Buông tay, chị vội choàng dậy, mở cửa...
Cẩm Huỳnh viết 1 năm trước
39 9
White
37 6
Làm thế nào để chỉ với một đoạn text vài trăm ký tự, bạn có thể làm ngốn vài gigabyte bộ nhớ và từ chối dịch vụ của một hệ thống dùng XML? _____ ...
Cẩm Huỳnh viết 4 tháng trước
37 6
White
34 25
Vừa rồi mình vừa tiết kiệm được $5 mỗi tháng sau khi migrate cái (Link) từ Digital Ocean sang Heroku Free Dyno. (Ảnh) Kết quả thật mĩ mãn vì hầu ...
Cẩm Huỳnh viết 12 tháng trước
34 25
Bài viết liên quan
White
3 0
Chú thích: Bài này đăng lần đầu năm 2009 để chia sẻ kinh nghiệm, sau khi tác giả viết xong thư viện closed source để bán. Hiện tại năm 2016 đã có t...
Ngoc Dao viết hơn 2 năm trước
3 0
White
10 0
Hadoop là cái gì vậy? “Hadoop là một framework nguồn mở viết bằng Java cho phép phát triển các ứng dụng phân tán có cường độ dữ liệu lớn một cách ...
nguyenduyhao1111 viết gần 2 năm trước
10 0
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
40 bài viết.
342 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!