Có một thông dịch viên sản xuất mã máy?


42

Tôi nghiên cứu các chủ đề của trình biên dịch và phiên dịch chuyên sâu. Tôi muốn kiểm tra xem sự hiểu biết cơ bản của tôi có đúng không, vì vậy hãy giả sử như sau:

Tôi có một ngôn ngữ gọi là "Foobish" và từ khóa của nó là

<OUTPUT> 'TEXT', <Number_of_Repeats>;

Vì vậy, nếu tôi muốn in ra bàn điều khiển 10 lần, tôi sẽ viết

OUTPUT 'Hello World', 10;

Xin chào World.foobish-file.

Bây giờ tôi viết một thông dịch viên bằng ngôn ngữ mà tôi chọn - C # trong trường hợp này:

using System;

namespace FoobishInterpreter
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            analyseAndTokenize(Hello World.foobish-file)//Pseudocode
            int repeats = Token[1];
            string outputString = Token[0];
            for (var i = 0; i < repeats; i++)
            {
                Console.WriteLine(outputString);
            }
        }
    }
}

Ở cấp độ thông dịch viên rất dễ dàng, trình thông dịch sẽ phân tích tệp tập lệnh, v.v. và thực thi ngôn ngữ foobish theo cách thực hiện của trình thông dịch.

Trình biên dịch sẽ tạo ngôn ngữ máy chạy trực tiếp trên phần cứng vật lý?

Vì vậy, một trình thông dịch không tạo ra ngôn ngữ máy, nhưng trình biên dịch có làm điều đó cho đầu vào của nó không?

Tôi có bất kỳ hiểu lầm theo cách cơ bản làm thế nào trình biên dịch và thông dịch viên làm việc?


21
Bạn nghĩ "trình biên dịch" C # làm gì? Như một gợi ý, nó không tạo ra mã máy.
Philip Kendall

3
Một trình biên dịch Java tạo mã cho JVM. Vì vậy, máy mục tiêu của trình biên dịch có thể là một máy ảo không được phần cứng thực thi trực tiếp. Sự khác biệt chính giữa trình thông dịch và trình biên dịch là trình biên dịch trước tiên kiểm tra và dịch toàn bộ mã nguồn thành ngôn ngữ máy đích. Mã được biên dịch này sau đó được thực thi bởi máy. Mặt khác, một thông dịch viên sẽ dịch và thực hiện các đoạn chương trình của bạn một cách nhanh chóng.
Giorgio

@Giorgio: Ý bạn là, giống như một JIT?
Robert Harvey

2
@RobertHarvey: Ý tôi là Trình biên dịch Java (javac): theo như tôi biết, nó tạo ra mã byte cho JVM. Và, một lần nữa AFAIK, JIT sau này (trong thời gian chạy) biên dịch một số mã byte được sử dụng rất thường xuyên thành ngôn ngữ máy gốc.
Giorgio

4
một trình biên dịch có nghĩa là dịch. Nó có thể phát ra tất cả các loại ngôn ngữ: c, lắp ráp, javascript, mã máy.
Esben Skov Pedersen

Câu trả lời:


77

Các thuật ngữ "trình thông dịch" và "trình biên dịch" mờ hơn nhiều so với trước đây. Nhiều năm trước, các trình biên dịch tạo ra mã máy sẽ được thực thi sau đó, trong khi các trình thông dịch ít nhiều "thực thi" mã nguồn trực tiếp. Vì vậy, hai thuật ngữ đó đã được hiểu rõ sau đó.

Nhưng ngày nay có nhiều biến thể về việc sử dụng "trình biên dịch" và "trình thông dịch". Ví dụ, VB6 "biên dịch" thành mã byte (một dạng Ngôn ngữ trung gian ), sau đó được "diễn giải" bởi VB Runtime. Một quy trình tương tự diễn ra trong C #, tạo ra CIL sau đó được thực hiện bởi Trình biên dịch đúng lúc (JIT) , mà ngày xưa, sẽ được coi là một trình thông dịch. Bạn có thể "đóng băng" đầu ra của JIT thành một tệp thực thi nhị phân thực tế bằng cách sử dụng NGen.exe , sản phẩm trong đó sẽ là kết quả của trình biên dịch trong những ngày cũ.

Vì vậy, câu trả lời cho câu hỏi của bạn gần như không đơn giản như trước đây.

Đọc thêm
Trình biên dịch so với Phiên dịch trên Wikipedia


6
@Giorgio: Hầu hết các thông dịch viên hiện nay không thực sự thực thi mã nguồn, mà là đầu ra của AST hoặc một cái gì đó tương tự. Trình biên dịch có một quy trình tương tự. Sự khác biệt gần như không rõ ràng như bạn nghĩ.
Robert Harvey

5
"Bạn có thể" đóng băng "đầu ra của JIT thành một tệp thực thi nhị phân thực tế bằng cách sử dụng NGen.exe, sản phẩm của nó sẽ là kết quả của trình biên dịch ngày xưa.": Nhưng ngày nay vẫn là kết quả của trình biên dịch (cụ thể là trình biên dịch đúng lúc). Nó không quan trọng khi trình biên dịch chạy, nhưng nó làm gì. Một trình biên dịch lấy đầu vào là một đại diện của một đoạn mã và đưa ra một đại diện mới. Một trình thông dịch sẽ xuất kết quả của việc thực thi đoạn mã đó. Đây là hai quy trình khác nhau, bất kể bạn trộn chúng như thế nào và khi bạn thực hiện những gì.
Giorgio

4
"Trình biên dịch" chỉ đơn giản là thuật ngữ họ đã chọn để đính kèm với GCC. Họ đã chọn không gọi NGen là trình biên dịch, mặc dù nó tạo ra mã máy, thay vào đó, tốt hơn là gắn thuật ngữ đó vào bước trước đó, có thể gọi là trình thông dịch, mặc dù nó cũng tạo ra mã máy (một số trình thông dịch cũng làm như vậy). Quan điểm của tôi là ngày nay không có nguyên tắc ràng buộc nào mà bạn có thể gọi để dứt khoát gọi một cái gì đó là trình biên dịch hoặc trình thông dịch, ngoài "đó là những gì họ đã luôn gọi nó."
Robert Harvey

4
Theo hiểu biết rất hạn chế của tôi, ngày nay CPU x86 chỉ là một nửa để trở thành công cụ JIT dựa trên phần cứng, với việc lắp ráp mang một mối quan hệ mờ nhạt với chính xác những gì được thực thi.
Leushenko

4
@RobertHarvey trong khi tôi đồng ý rằng không có đường phân chia rõ ràng giữa các kỹ thuật được sử dụng trong trình thông dịch và trình biên dịch, có một sự phân chia khá rõ ràng về chức năng: nếu kết quả của việc thực thi một công cụ nhất định với mã của chương trình vì đầu vào là việc thực thi điều đó chương trình, công cụ là một thông dịch viên. Nếu kết quả là đầu ra của một bản dịch chương trình sang dạng ít trừu tượng hơn, thì đó là một trình biên dịch. Nếu kết quả là dịch sang dạng trừu tượng hơn, nó sẽ là một trình dịch ngược. Tuy nhiên, trường hợp có nhiều hơn một trong những kết quả này là mơ hồ.
Jules

34

Tóm tắt tôi đưa ra dưới đây dựa trên "Trình biên dịch, Nguyên tắc, Kỹ thuật & Công cụ", Aho, Lam, Sethi, Ullman, (Pearson International Edition, 2007), trang 1, 2, với việc bổ sung một số ý tưởng của riêng tôi.

Hai cơ chế cơ bản để xử lý một chương trình là biên dịchgiải thích .

Quá trình biên dịch lấy đầu vào là chương trình nguồn theo ngôn ngữ đã cho và xuất chương trình đích bằng ngôn ngữ đích.

source program --> | compiler | --> target program

Nếu ngôn ngữ đích là mã máy, nó có thể được thực thi trực tiếp trên một số bộ xử lý:

input --> | target program | --> output

Quá trình biên dịch bao gồm quét và dịch toàn bộ chương trình đầu vào (hoặc mô-đun) và không liên quan đến việc thực hiện nó.

Giải thích lấy làm đầu vào của chương trình nguồn và đầu vào của nó, và tạo ra đầu ra của chương trình nguồn

source program, input --> | interpreter | --> output

Giải thích thường liên quan đến việc xử lý (phân tích và thực hiện) một câu lệnh chương trình tại một thời điểm.

Trong thực tế, nhiều bộ xử lý ngôn ngữ sử dụng kết hợp cả hai cách tiếp cận. Ví dụ, các chương trình Java được dịch lần đầu tiên (được biên dịch) thành một chương trình trung gian (mã byte):

source program --> | translator | --> intermediate program

đầu ra của bước này sau đó được thực thi (diễn giải) bởi một máy ảo:

intermediate program + input --> | virtual machine | --> output

Để làm phức tạp mọi thứ hơn nữa, JVM có thể thực hiện biên dịch đúng lúc trong thời gian chạy để chuyển đổi mã byte sang định dạng khác, sau đó được thực thi.

Ngoài ra, ngay cả khi bạn biên dịch sang ngôn ngữ máy, vẫn có một trình thông dịch chạy tệp nhị phân của bạn được bộ xử lý bên dưới triển khai. Do đó, ngay cả trong trường hợp này, bạn đang sử dụng kết hợp biên dịch + giải thích.

Vì vậy, các hệ thống thực sử dụng kết hợp cả hai nên rất khó để nói liệu bộ xử lý ngôn ngữ nhất định là trình biên dịch hay trình thông dịch, bởi vì nó có thể sẽ sử dụng cả hai cơ chế ở các giai đoạn xử lý khác nhau. Trong trường hợp này có lẽ sẽ thích hợp hơn để sử dụng một thuật ngữ khác, trung tính hơn.

Tuy nhiên, biên dịch và giải thích là hai loại xử lý riêng biệt, như được mô tả trong các sơ đồ trên,

Để trả lời các câu hỏi ban đầu.

Một trình biên dịch sẽ tạo ra ngôn ngữ máy chạy trực tiếp trên phần cứng vật lý?

Không nhất thiết, trình biên dịch dịch một chương trình được viết cho máy M1 thành chương trình tương đương được viết cho máy M2. Máy đích có thể được thực hiện trong phần cứng hoặc là máy ảo. Về mặt khái niệm không có sự khác biệt. Điểm quan trọng là trình biên dịch nhìn vào một đoạn mã và dịch nó sang ngôn ngữ khác mà không thực hiện nó.

Vì vậy, một trình thông dịch không tạo ra ngôn ngữ máy nhưng trình biên dịch sẽ làm điều đó cho đầu vào của nó?

Nếu bằng cách tạo ra bạn đang đề cập đến đầu ra, thì trình biên dịch sẽ tạo ra một chương trình đích có thể bằng ngôn ngữ máy, một trình thông dịch thì không.


7
Nói cách khác: trình thông dịch lấy chương trình P và tạo đầu ra O, trình biên dịch lấy P và tạo chương trình P tạo ra O; trình thông dịch thường bao gồm các thành phần là trình biên dịch (ví dụ: mã byte, biểu diễn trung gian hoặc hướng dẫn máy JIT) và tương tự, trình biên dịch có thể bao gồm trình thông dịch (ví dụ: để đánh giá tính toán thời gian biên dịch).
Jon Purdy

"Trình biên dịch có thể bao gồm một trình thông dịch (ví dụ: để đánh giá các tính toán thời gian biên dịch)": Điểm tốt. Tôi đoán các mẫu Lisp và các mẫu C ++ có thể được xử lý trước theo cách này.
Giorgio

Thậm chí đơn giản hơn, bộ tiền xử lý C biên dịch mã nguồn C với các lệnh CPP thành C đơn giản và bao gồm một trình thông dịch cho các biểu thức boolean như defined A && !defined B.
Jon Purdy

@JonPurdy Tôi đồng ý với điều đó, nhưng tôi cũng sẽ thêm một lớp, "thông dịch viên truyền thống", không sử dụng các biểu diễn trung gian ngoài có lẽ là phiên bản nguồn được mã hóa. Ví dụ sẽ là shell, nhiều BASIC, Lisp cổ điển, Tcl trước 8.0 và bc.
hobbs

1
@naxa - xem câu trả lời của Lawrence và nhận xét của Paul Draper về các loại trình biên dịch. Trình biên dịch là một loại trình biên dịch đặc biệt trong đó (1) ngôn ngữ đầu ra được dùng để thực hiện trực tiếp bởi một máy hoặc máy ảo và (2) có một sự tương ứng một-một rất đơn giản giữa các câu lệnh đầu vào và các lệnh đầu ra.
Jules

22

Một trình biên dịch sẽ tạo ra ngôn ngữ máy

Số Một trình biên dịch chỉ đơn giản là một chương trình mà mất như đầu vào của nó một chương trình viết bằng ngôn ngữ Một và sản xuất như sản lượng của nó một chương trình ngữ nghĩa tương đương trong ngôn ngữ B . Ngôn ngữ B có thể là bất cứ thứ gì, nó không phải là ngôn ngữ máy.

Trình biên dịch có thể biên dịch từ ngôn ngữ cấp cao sang ngôn ngữ cấp cao khác (ví dụ: GWT, biên dịch Java thành ECMAScript), từ ngôn ngữ cấp cao sang ngôn ngữ cấp thấp (ví dụ: Gambit, biên dịch Lược đồ sang C), từ ngôn ngữ cấp cao sang mã máy (ví dụ: GCJ, biên dịch Java thành mã gốc), từ ngôn ngữ cấp thấp sang ngôn ngữ cấp cao (ví dụ: Clue, biên dịch C sang Java, Lua, Perl, ECMAScript và Common Lisp), từ ngôn ngữ cấp thấp sang ngôn ngữ cấp thấp khác (ví dụ SDK Android, biên dịch mã byte JVML sang mã byte Dalvik), từ ngôn ngữ cấp thấp sang mã máy (ví dụ: trình biên dịch C1X là một phần của HotSpot, trong đó biên dịch mã byte JVML thành mã máy), mã máy thành ngôn ngữ cấp cao (bất kỳ cái gọi là "trình dịch ngược", cũng là Emscripten, biên dịch mã máy LLVM thành ECMAScript),mã máy thành ngôn ngữ cấp thấp (ví dụ: trình biên dịch JIT trong JPC, mã biên dịch mã gốc x86 thành mã byte JVML) và mã gốc thành mã gốc (ví dụ: trình biên dịch JIT trong PearPC, mã biên dịch mã gốc PowerPC thành mã gốc x86).

Cũng lưu ý rằng "mã máy" là một thuật ngữ thực sự mờ vì nhiều lý do. Ví dụ, có các CPU thực thi mã byte JVM và có các trình thông dịch phần mềm cho mã máy x86. Vì vậy, những gì làm cho một "mã máy gốc" nhưng không phải là cái khác? Ngoài ra, mọi ngôn ngữ là mã cho một máy trừu tượng cho ngôn ngữ đó.

Có nhiều tên chuyên biệt cho trình biên dịch thực hiện các chức năng đặc biệt. Mặc dù thực tế đây là những tên chuyên biệt, tất cả chúng vẫn là trình biên dịch, chỉ là các loại trình biên dịch đặc biệt:

  • nếu ngôn ngữ A được coi là có mức độ trừu tượng tương đương với ngôn ngữ B , trình biên dịch có thể được gọi là bộ chuyển mã (ví dụ: bộ chuyển mã Ruby-to-ECMAScript-transpiler hoặc ECMAScript2015-to-ECMAScript5-transpiler)
  • nếu ngôn ngữ A được coi là ở mức độ trừu tượng thấp hơn ngôn ngữ B , trình biên dịch có thể được gọi là trình dịch ngược (ví dụ: trình dịch ngược x86-machine-code-to-C-decompiler)
  • nếu ngôn ngữ A == ngôn ngữ B , trình biên dịch có thể được gọi là trình tối ưu hóa , obfuscator hoặc minifier (tùy thuộc vào chức năng cụ thể của trình biên dịch)

Mà chạy trên phần cứng vật lý trực tiếp?

Không cần thiết. Nó có thể được chạy trong một trình thông dịch hoặc trong VM. Nó có thể được biên dịch thêm sang một ngôn ngữ khác.

Vì vậy, một trình thông dịch không tạo ra ngôn ngữ máy nhưng trình biên dịch sẽ làm điều đó cho đầu vào của nó?

Một thông dịch viên không sản xuất bất cứ điều gì. Nó chỉ chạy chương trình.

Một trình biên dịch tạo ra một cái gì đó, nhưng nó không nhất thiết phải là ngôn ngữ máy, nó có thể là bất kỳ ngôn ngữ nào. Nó thậm chí có thể là ngôn ngữ giống như ngôn ngữ đầu vào! Ví dụ, Supercompilers, LLC có trình biên dịch lấy Java làm đầu vào và tạo ra Java được tối ưu hóa làm đầu ra của nó. Có nhiều trình biên dịch ECMAScript lấy ECMAScript làm đầu vào của chúng và tạo ra ECMAScript được tối ưu hóa, thu nhỏ và bị xáo trộn làm đầu ra của chúng.


Bạn cũng có thể quan tâm:


16

Tôi nghĩ bạn nên bỏ hoàn toàn khái niệm "trình biên dịch so với trình thông dịch", bởi vì đó là một sự phân đôi giả.

  • Một trình biên dịch là một biến áp : Nó biến một chương trình máy tính viết bằng một ngôn ngữ nguồn và kết quả đầu ra tương đương trong một ngôn ngữ mục tiêu . Thông thường, ngôn ngữ nguồn ở cấp độ cao hơn ngôn ngữ đích - và nếu theo cách khác, chúng ta thường gọi loại biến áp đó là trình dịch ngược .
  • Một thông dịch viên là một công cụ thực thi . Nó thực thi một chương trình máy tính được viết bằng một ngôn ngữ, theo đặc điểm kỹ thuật của ngôn ngữ đó. Chúng tôi chủ yếu sử dụng thuật ngữ cho phần mềm (nhưng theo một cách nào đó, CPU cổ điển có thể được xem như là một "trình thông dịch" dựa trên phần cứng cho mã máy của nó).

Từ tập thể để làm cho một ngôn ngữ lập trình trừu tượng trở nên hữu ích trong thế giới thực là triển khai .

Trước đây, việc triển khai ngôn ngữ lập trình thường chỉ bao gồm một trình biên dịch (và CPU mà nó tạo mã) hoặc chỉ là một trình thông dịch - vì vậy có thể có vẻ như hai loại công cụ này là loại trừ lẫn nhau. Ngày nay, bạn có thể thấy rõ rằng đây không phải là trường hợp (và nó chưa bao giờ bắt đầu). Việc thực hiện ngôn ngữ lập trình phức tạp và cố gắng chuyển tên "trình biên dịch" hoặc "trình thông dịch" sang nó, thường sẽ dẫn đến kết quả không nhất quán hoặc không nhất quán.

Việc triển khai ngôn ngữ lập trình đơn lẻ có thể bao gồm bất kỳ số lượng trình biên dịch và trình thông dịch , thường ở nhiều dạng (độc lập, đang hoạt động), bất kỳ số lượng công cụ nào khác, như máy phân tíchtối ưu hóa tĩnh , và bất kỳ số bước nào. Nó thậm chí có thể bao gồm toàn bộ việc triển khai bất kỳ số lượng ngôn ngữ trung gian nào (có thể không liên quan đến ngôn ngữ đang được triển khai).

Ví dụ về các kế hoạch thực hiện bao gồm:

  • Trình biên dịch AC biến đổi mã máy C thành x86 và CPU x86 thực thi mã đó.
  • Trình biên dịch AC biến đổi C thành LLVM IR, trình biên dịch phụ trợ LLVM chuyển đổi LLVM IR thành mã máy x86 và CPU x86 thực thi mã đó.
  • Trình biên dịch AC biến đổi C thành LLVM IR và trình thông dịch LLVM thực thi LLVM IR.
  • Trình biên dịch Java chuyển đổi Java thành mã byte JVM và JRE với trình thông dịch thực thi mã đó.
  • Trình biên dịch Java chuyển đổi Java thành mã byte JVM và JRE có cả trình thông dịch thực thi một số phần của mã đó và trình biên dịch chuyển đổi các phần khác của mã đó thành mã máy x86 và CPU x86 thực thi mã đó.
  • Một trình biên dịch Java chuyển đổi Java thành mã byte JVM và CPU ARM thực thi mã đó.
  • Trình biên dịch AC # biến đổi C # thành CIL, CLR với trình biên dịch biến CIL thành mã máy x86 và CPU x86 thực thi mã đó.
  • Một thông dịch viên Ruby thực thi Ruby.
  • Một môi trường Ruby có cả trình thông dịch thực thi Ruby và trình biên dịch biến đổi mã Ruby thành mã máy x86 và CPU x86 thực thi mã đó.

... và cứ thế.


+1 để chỉ ra rằng ngay cả các bảng mã được thiết kế cho biểu diễn trung gian (ví dụ: mã byte java) có thể có các triển khai phần cứng.
Jules

7

Mặc dù các dòng giữa trình biên dịch và trình thông dịch đã mờ dần theo thời gian, người ta vẫn có thể vẽ một đường giữa chúng bằng cách xem xét ngữ nghĩa của những gì chương trình nên làm và trình biên dịch / trình thông dịch làm gì.

Một trình biên dịch sẽ tạo ra một chương trình khác (thường là bằng ngôn ngữ cấp thấp hơn như mã máy), nếu chương trình đó được chạy, sẽ làm những gì chương trình của bạn nên làm.

Một thông dịch viên sẽ làm những gì chương trình của bạn nên làm.

Với các định nghĩa này, những nơi mà nó trở nên mờ nhạt là trường hợp trình biên dịch / trình thông dịch của bạn có thể được coi là làm những việc khác nhau tùy thuộc vào cách bạn nhìn vào nó. Ví dụ: Python lấy mã Python của bạn và biên dịch nó thành mã byte Python đã biên dịch. Nếu mã byte Python này được chạy thông qua trình thông dịch mã byte Python , thì nó sẽ làm những gì chương trình của bạn phải làm. Tuy nhiên, trong hầu hết các tình huống, các nhà phát triển Python nghĩ rằng cả hai bước đó đều được thực hiện trong một bước lớn, vì vậy họ chọn nghĩ trình thông dịch CPythondiễn giải mã nguồn của họ và thực tế là nó được biên dịch theo cách được coi là một chi tiết triển khai . Theo cách này, tất cả chỉ là vấn đề về quan điểm.


5

Đây là một định hướng khái niệm đơn giản giữa trình biên dịch và trình thông dịch.

Xem xét 3 ngôn ngữ: ngôn ngữ lập trình , P (chương trình được viết bằng gì); ngôn ngữ miền , D (cho những gì diễn ra với chương trình đang chạy); và ngôn ngữ đích , T (một số ngôn ngữ thứ ba).

Về mặt khái niệm,

  • một trình biên dịch dịch P sang T để bạn có thể đánh giá T (D); trong khi

  • một thông dịch viên đánh giá P (D) trực tiếp.


1
Hầu hết các thông dịch viên hiện đại không thực sự đánh giá trực tiếp ngôn ngữ nguồn, mà là một số đại diện trung gian của ngôn ngữ nguồn.
Robert Harvey

4
@RobertHarvey Điều đó không thay đổi sự phân biệt khái niệm giữa các điều khoản.
Lawrence

1
Vì vậy, những gì bạn thực sự đề cập đến như là thông dịch viên là phần đánh giá đại diện trung gian. Phần tạo ra biểu diễn trung gian là một trình biên dịch , theo định nghĩa của bạn.
Robert Harvey

6
@RobertHarvey Không thực sự. Các điều khoản phụ thuộc vào mức độ trừu tượng mà bạn đang làm việc. Nếu bạn nhìn bên dưới, công cụ có thể làm bất cứ điều gì. Bằng cách tương tự, giả sử bạn đến một quốc gia nước ngoài và mang theo một người bạn song ngữ Bob đi cùng. Nếu bạn giao tiếp với người dân địa phương bằng cách nói chuyện với Bob, người lần lượt nói chuyện với người dân địa phương, Bob đóng vai trò là người phiên dịch cho bạn (ngay cả khi anh ta viết nguệch ngoạc bằng ngôn ngữ của họ trước khi nói chuyện). Nếu bạn hỏi Bob cụm từ và Bob viết chúng bằng tiếng nước ngoài, và bạn giao tiếp với người dân địa phương bằng cách tham khảo những bài viết đó (không phải Bob) Bob đóng vai trò là người biên dịch cho bạn.
Lawrence

1
Câu trả lời tuyệt vời. Đáng chú ý: Ngày nay bạn có thể nghe thấy "transpiler". Đó là một trình biên dịch trong đó P và T là các mức độ trừu tượng tương tự nhau, đối với một số định nghĩa tương tự. (Ví dụ: bộ chuyển mã ES5 sang ES6.)
Paul Draper
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.