+4

Cách Ruby thực thi code

I. Giới Thiệu:

Có bao giờ bạn trong lúc code bạn tư đặt câu hỏi? 1 đoạn code đơn giản

1 + 2 = 3

Ruby sẽ thực hiện như thế nào chưa? Nó có thực sự tính toán đơn giản như bạn nghĩ chỉ là 1 + 2 = 3 hay phải đi qua nhiều bước trước khi trả ra cho chúng ta kết quả không? Vậy bạn đã từng tìm hiểu về cách ruby thực thi 1 đoạn code chưa? Tôi sẽ trả lời câu hỏi trên cho bạn biết rằng: Ruby thực thi và chuyển hóa thành từng format khác nhau chính xác là 3 lần

  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 Node
  3. Sau đó compile thành bytecodes là một tập các lệnh thực thi cấp thấp. Đây chính là tập lệnh được chạy trong máy áo Ruby (Ruby Virtual Machine).

II. Tokenizing - Thuật toán tokenize

Tôi sẽ cho 1 đoạn code:

1.upto(3) do |x|
    puts x
 end

Khi ta run đoạn code ruby trên việc đầu tiên ruby sẽ split nó thành những kí tự. Hàng 1 ta có: [1][.][u][p][t][o][(][3][)][][d][o][][|][x][|] Ta có 1 con trỏ đọc từng ký tự của hàng 1 Con trở tới 1. Ruby nhân ra rằng đây là số nên nó sẽ tiếp tục di chuyển con trỏ cho tới khi gặp ký tự không phải là số Khi con trỏ tiếp tục dịch chuyển và gặp . Lúc này nó cho rằng . cũng có thể là 1 phần của số thực nên nó tiếp tục dich chuyển Tới khi gặp ký tự u lúc này ruby nhận ra đây không phải là số, và sau dấu . không còn ký tự số nào nữa. Đến đây con trỏ lui lại 1 ký tự và convert tất cả các ký tự số nó đã đi qua thành (tINTERGER) token. Ta có:

(tINTERGER)[.][u][p][t][o][(][3][)][][d][o][][|][x][|]

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) (.)[u][p][t][o][(][3][)][][d][o][][|][x][|]

Và như thế nó tiếp tục trỏ qua upto và convert nó thành (tIDENTIFIER) Ta có:

(tINTEGER) (.)(tIDENTIFIER)[(][3][)][][d][o][][|][x][|]

Chú ý: IDENTIFER không phải là reserved keyword trong Ruby, nó dùng để chỉ một biến (var), một hàm (function), hay một method. Tiếp theo ruby qua (3) và convert nó thành

(tINTEGER) (.)(tIDENTIFIER)(()(tINTEGER)())[d][o][][|][x][|]

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

(tINTEGER) (.)(tIDENTIFIER)(()(tINTEGER)())(keyword_do)[][|][x][|]

Cuối cùng Ruby cũng token xong hàng 1 của đoạn code Ruby trên.

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

III. Ruby parsing

Sau khi tokenize đoạn code Ruby của bạn thành một đống token rồi, Ruby sẽ tiến hành parse các token đó thành các câu, cụm có nghĩa với nó.

Như mọi ngôn ngữ khác, Ruby sử dụng một parser generator tên là Bison (có cơ hội mình sẽ viết một bài kĩ hơn về bison). Như các bạn thấy ở hình trên, Bison sẽ sinh ra parser parse.c thông qua bộ luật được định nghĩa ở parse.y, sau đó parse.c sẽ thực hiện parse code Ruby thành các AST Nodes và biên dịch nó thành byte code, để máy ảo Ruby có thể thực thi.

IV. Ruby Compilation

  • YARV instruction YARV bản chất là một Stack-oriented VM, nói một cách khác YARV nó sử dụng một value stack (chứa các args và giá trị trả về) để thực thi "YARV instruction" (từ đây mình sẽ gọi là instruction cho nhanh), nên các instruction được xây dựng tận dụng stack này (push giá trị tính được vào stack hoặc pop giá trị cần tìm ra khỏi stack).

    Ruby là một ngôn ngữ OOP hoàn toàn, tất cả các lệnh gọi đều có dạng receiver.method(arguments), vì thế instructions được sinh ra luôn đi theo nguyên tắc sau.

    • Đẩy receiver vào stack.
    • Đẩy arguments vào stack.
    • Đẩy method vào stack.
  • Local Table

    • Khi compiler chạy, thông tin về các biến, tham số được Ruby lưu ở một nơi khác gọi là Local Table. Để sử dụng local table Ruby dùng hai lệnh setlocal (dùng để gán) và getlocal (dùng để tham chiếu)

Tài liệu tham khảo:

Ruby Under a Microscope: An Illustrated Guide to Ruby Internals


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí