Làm thế nào để viết một trình thông dịch lệnh / trình phân tích cú pháp?


22

Vấn đề: Chạy các lệnh dưới dạng một chuỗi.

  • ví dụ lệnh:

    /user/files/ list all; tương đương với: /user/files/ ls -la;

  • một số khác:

    post tw fb "HOW DO YOU STOP THE TICKLE MONSTER?;"

tương đương với: post -tf "HOW DO YOU STOP THE TICKLE MONSTER?;"

Giải pháp tạm thời:

tokenize string(string, array);

switch(first item in array) {
    case "command":
        if ( argument1 > stuff) {
           // do the actual work;
        }
}

Các vấn đề tôi thấy trong giải pháp này là:

  • Không có lỗi kiểm tra nào ngoài ifs lồng nhau - khác bên trong mỗi trường hợp. Kịch bản trở nên rất lớn và khó khăn.
  • Các lệnh và phản hồi được mã hóa cứng.
  • Không có cách nào để biết nếu cờ là tham số chính xác hoặc thiếu.
  • Thiếu thông minh để đề xuất "bạn có thể muốn chạy lệnh $".

Và điều cuối cùng tôi không thể giải quyết là các từ đồng nghĩa trong các bảng mã khác nhau, ví dụ:

case command:
case command_in_hebrew:
    do stuff;
break;

Cái cuối cùng có thể là tầm thường, nhưng tốt, điều tôi muốn thấy là những khoản tài trợ vững chắc của loại chương trình này.

Tôi hiện đang lập trình điều này trong PHP nhưng có thể thực hiện nó trong PERL.


Tôi hoàn toàn không thấy điều này liên quan cụ thể đến PHP. Có rất nhiều chủ đề về trình thông dịch / trình biên dịch-chủ đề này trên SO và SE.
Raffael

3
Không ai đề cập đến getopt?
Anton Barkovsky

@AntonBarkovsky: Tôi đã làm. Xem các liên kết của tôi. Tôi nghĩ rằng những câu trả lời như của Ubermensch chỉ là quá phức tạp đối với những gì OP đang cố gắng thực hiện.
quentin-starin

1
Tôi cũng đã trích dẫn một cách tiếp cận đơn giản bằng RegExp. Câu trả lời cũng được cập nhật
Ubermensch

Không đề cập đến bất kỳ progr cụ thể. lang. bạn có thể thêm thẻ "c", thẻ "ruby", thẻ "php", có thể có lib mã nguồn mở, lib chuẩn. hoặc "thường được sử dụng, chưa phải là lib chuẩn." cho progr của bạn. lang.
umlcat

Câu trả lời:


14

Hãy để tôi thừa nhận thẳng thắn, xây dựng trình phân tích cú pháp là một công việc tẻ nhạt và gần với công nghệ trình biên dịch nhưng xây dựng một công cụ sẽ trở thành một cuộc phiêu lưu tốt. Và một trình phân tích cú pháp đi kèm với thông dịch viên. Vì vậy, bạn phải xây dựng cả hai.

Giới thiệu nhanh về trình phân tích cú pháp và trình thông dịch

Đây không phải là quá kỹ thuật. Vì vậy, các chuyên gia đừng băn khoăn với tôi.

Khi bạn đưa một số đầu vào vào một thiết bị đầu cuối, thiết bị đầu cuối sẽ chia đầu vào thành nhiều đơn vị. Đầu vào được gọi là biểu thức và nhiều đơn vị được gọi là mã thông báo. Các mã thông báo này có thể là toán tử hoặc ký hiệu. Vì vậy, nếu bạn nhập 4 + 5 vào máy tính, biểu thức này sẽ được chia thành ba mã thông báo 4, +, 5. Điểm cộng được coi là toán tử trong khi 4 và 5 ký hiệu. Điều này được truyền cho một chương trình (coi đây là một trình thông dịch) có chứa định nghĩa cho các toán tử. Dựa trên định nghĩa (trong trường hợp của chúng tôi, thêm), nó thêm hai ký hiệu và trả kết quả cho thiết bị đầu cuối. Tất cả các trình biên dịch được dựa trên công nghệ này. Chương trình phân tách một biểu thức thành nhiều mã thông báo được gọi là lexer và chương trình chuyển đổi các mã thông báo này thành các thẻ để xử lý và thực thi thêm được gọi là trình phân tích cú pháp.

Lex và Yacc là các hình thức chính tắc để xây dựng các từ vựng và trình phân tích cú pháp dựa trên ngữ pháp BNF theo C và đó là tùy chọn được đề xuất. Hầu hết các trình phân tích cú pháp là một bản sao của Lex và Yacc.

Các bước trong việc xây dựng một trình phân tích cú pháp / thông tin liên lạc

  1. Phân loại mã thông báo của bạn thành biểu tượng, toán tử và từ khóa (từ khóa là toán tử)
  2. Xây dựng ngữ pháp của bạn bằng mẫu BNF
  3. Viết các hàm phân tích cú pháp cho các hoạt động của bạn
  4. Biên dịch nó chạy như một chương trình

Vì vậy, trong trường hợp trên, mã thông báo bổ sung của bạn sẽ là bất kỳ chữ số nào và dấu cộng với định nghĩa phải làm gì với dấu cộng trong từ vựng

Ghi chú và Mẹo

  • Chọn một kỹ thuật phân tích cú pháp đánh giá từ trái sang phải LALR
  • Đọc cuốn sách rồng này trên Trình biên dịch để cảm nhận về nó. Cá nhân tôi chưa hoàn thành cuốn sách
  • Đây liên kết sẽ cung cấp một cái nhìn sâu sắc siêu nhanh vào Lex và Yacc dưới Python

Một cách tiếp cận đơn giản

Nếu bạn chỉ cần một cơ chế phân tích cú pháp đơn giản với các hàm giới hạn, hãy biến yêu cầu của bạn thành Biểu thức chính quy và chỉ cần tạo ra một loạt các hàm. Để minh họa, giả sử một trình phân tích cú pháp đơn giản cho bốn hàm số học. Vì vậy, bạn sẽ là người gọi toán tử trước và sau đó là danh sách các hàm (tương tự như lisp) theo kiểu (+ 4 5)hoặc (add [4,5])sau đó bạn có thể sử dụng RegExp đơn giản để lấy danh sách các toán tử và ký hiệu được vận hành theo.

Hầu hết các trường hợp phổ biến có thể được giải quyết dễ dàng bằng phương pháp này. Nhược điểm là bạn không thể có nhiều biểu thức lồng nhau với cú pháp rõ ràng và bạn không thể có các hàm bậc cao dễ dàng hơn.


2
Đây là một trong những cách khó nhất có thể. Tách các lexing và phân tích cú pháp, v.v. - có lẽ hữu ích cho việc triển khai trình phân tích cú pháp hiệu suất cao cho một ngôn ngữ rất phức tạp nhưng cổ xưa. Trong thế giới hiện đại, phân tích cú pháp không giới hạn là một tùy chọn mặc định đơn giản nhất. Các bộ kết hợp phân tích cú pháp hoặc eDSL dễ sử dụng hơn các bộ tiền xử lý chuyên dụng như Yacc.
SK-logic

Đồng ý với SK-logic nhưng vì cần có câu trả lời chi tiết chung, tôi đã đề xuất Lex và Yacc và một số kiến ​​thức cơ bản về trình phân tích cú pháp. getopts được đề xuất bởi Anton cũng là một lựa chọn đơn giản hơn.
Ubermensch

đó là những gì tôi đã nói - lex và yacc là một trong những cách khó nhất để phân tích cú pháp và thậm chí không đủ chung chung. Phân tích cú pháp nhanh hơn (ví dụ, packrat, hoặc giống như Parsec) đơn giản hơn nhiều cho một trường hợp chung. Và cuốn sách Rồng không phải là một giới thiệu rất hữu ích để phân tích cú pháp nữa - nó đã quá lỗi thời.
SK-logic

@ SK-logic Bạn có thể giới thiệu một cuốn sách cập nhật tốt hơn. Nó dường như bao gồm tất cả những điều cơ bản cho một người đang cố gắng hiểu phân tích cú pháp (ít nhất là trong nhận thức của tôi). Về lex và yacc, mặc dù khó, nhưng nó được sử dụng rộng rãi và rất nhiều ngôn ngữ lập trình cung cấp cho nó.
Ubermensch

1
@ alfa64: hãy chắc chắn cho chúng tôi biết sau đó khi bạn thực sự mã hóa một giải pháp dựa trên câu trả lời này
quentin-starin

7

Đầu tiên, khi nói về ngữ pháp hoặc cách xác định các đối số, đừng phát minh ra các đối số của riêng bạn. Các tiêu chuẩn GNU kiểu là đã rất phổ biến và nổi tiếng.

Thứ hai, vì bạn đang sử dụng một tiêu chuẩn được chấp nhận, không nên phát minh lại bánh xe. Sử dụng một thư viện hiện có để làm điều đó cho bạn. Nếu bạn sử dụng đối số kiểu GNU, gần như chắc chắn đã có một thư viện trưởng thành trong ngôn ngữ bạn chọn. Ví dụ: c # , php , c .

Một thư viện phân tích tùy chọn tốt thậm chí sẽ in trợ giúp được định dạng trên các tùy chọn có sẵn cho bạn.

EDIT 12/27

Có vẻ như bạn đang làm cho điều này trở nên phức tạp hơn nó.

Khi bạn nhìn vào một dòng lệnh, nó thực sự khá đơn giản. Nó chỉ là các tùy chọn và đối số cho các tùy chọn đó. Có rất ít vấn đề phức tạp. Tùy chọn có thể có bí danh. Đối số có thể là danh sách các đối số.

Một vấn đề với câu hỏi của bạn là bạn chưa thực sự chỉ định bất kỳ quy tắc nào cho loại dòng lệnh bạn muốn xử lý. Tôi đã đề xuất tiêu chuẩn GNU và các ví dụ của bạn gần với điều đó (mặc dù tôi không thực sự hiểu ví dụ đầu tiên của bạn với đường dẫn là mục đầu tiên?).

Nếu chúng ta đang nói GNU, bất kỳ tùy chọn đơn lẻ nào cũng chỉ có thể có dạng dài và dạng ngắn (ký tự đơn) dưới dạng bí danh. Bất kỳ đối số có chứa một không gian phải được bao quanh trong dấu ngoặc kép. Nhiều tùy chọn hình thức ngắn có thể được xâu chuỗi. (Các) tùy chọn biểu mẫu ngắn phải được tiến hành bằng một dấu gạch ngang đơn, dạng dài bằng hai dấu gạch ngang. Chỉ cuối cùng của các tùy chọn hình thức chuỗi ngắn có thể có một đối số.

Tất cả đều rất đơn giản. Tất cả đều rất phổ biến. Cũng được thực hiện trong mọi ngôn ngữ bạn có thể tìm thấy, có thể hơn năm lần.

Đừng viết nó. Sử dụng những gì đã được viết.

Trừ khi bạn có một cái gì đó trong tâm trí ngoài các đối số dòng lệnh tiêu chuẩn, chỉ cần sử dụng một trong NHIỀU thư viện đã được thử nghiệm, đã thực hiện việc này.

Biến chứng là gì?


3
Luôn luôn, luôn tận dụng cộng đồng nguồn mở.
Spencer Rathbun

bạn đã thử getoptionkit chưa?
alfa64

Không, tôi đã không làm việc trong php trong một vài năm. Cũng có thể có các thư viện php khác. Tôi đã sử dụng thư viện trình phân tích cú pháp dòng lệnh c # mà tôi liên kết đến.
quentin-starin

4

Bạn đã thử một cái gì đó như http://qntm.org/loco chưa? Cách tiếp cận này sạch sẽ hơn nhiều so với bất kỳ quảng cáo viết tay nào, nhưng sẽ không yêu cầu một công cụ tạo mã độc lập như Lemon.

EDIT: Và một mẹo chung để xử lý các dòng lệnh với cú pháp phức tạp là kết hợp các đối số lại thành một chuỗi được phân tách bằng khoảng trắng và sau đó phân tích nó đúng như thể đó là một biểu thức của một ngôn ngữ cụ thể theo miền.


+1 liên kết đẹp, tôi tự hỏi nếu nó có sẵn trên github hoặc cái gì khác. Và những gì về các điều khoản sử dụng?
hakre

1

Bạn chưa đưa ra nhiều chi tiết cụ thể về ngữ pháp của bạn, chỉ là một số ví dụ. Những gì tôi có thể thấy là có một số chuỗi, khoảng trắng và một (có lẽ, ví dụ của bạn không quan tâm trong câu hỏi của bạn) chuỗi trích dẫn kép và sau đó là một chuỗi ";" cuối cùng.

Có vẻ như điều này có thể tương tự như cú pháp PHP. Nếu vậy, PHP đi kèm với một trình phân tích cú pháp, bạn có thể sử dụng lại và sau đó xác nhận cụ thể hơn. Cuối cùng, bạn cần phải xử lý các mã thông báo, nhưng có vẻ như điều này chỉ đơn giản là từ trái sang phải nên thực sự chỉ là một lần lặp trên tất cả các mã thông báo.

Một số ví dụ để sử dụng lại trình phân tích cú pháp mã thông báo PHP ( token_get_all) được đưa ra trong các câu trả lời cho các câu hỏi sau:

Cả hai ví dụ đều chứa một trình phân tích cú pháp đơn giản, có lẽ một cái gì đó giống như những cái đó phù hợp với kịch bản của bạn.


vâng, tôi đã vội vã các công cụ ngữ pháp, tôi sẽ thêm nó ngay bây giờ.
alfa64

1

Nếu nhu cầu của bạn đơn giản và cả hai bạn đều có thời gian và hứng thú với nó, tôi sẽ đi ngược lại với hạt sạn ở đây và nói rằng đừng ngại viết trình phân tích cú pháp của riêng bạn. Đó là một kinh nghiệm học tập tốt, nếu không có gì khác. Nếu bạn có các yêu cầu phức tạp hơn - các lệnh gọi hàm, mảng, v.v. - chỉ cần lưu ý rằng làm như vậy có thể mất nhiều thời gian. Một trong những điểm tích cực của việc tự mình thực hiện là sẽ không có vấn đề gì về việc tích hợp với hệ thống của bạn. Nhược điểm là, tất nhiên, tất cả các vít lên là lỗi của bạn.

Tuy nhiên, làm việc chống lại mã thông báo, không sử dụng các lệnh được mã hóa cứng. Sau đó, vấn đề với các lệnh âm thanh tương tự biến mất.

Mọi người luôn giới thiệu cuốn sách về rồng, nhưng tôi luôn thấy "Trình biên dịch và phiên dịch" của Ronald Mak là một phần giới thiệu tốt hơn.


0

Tôi đã viết các chương trình hoạt động như thế. Một là bot IRC có cú pháp lệnh tương tự. Có một tập tin lớn đó là một tuyên bố chuyển đổi lớn. Nó hoạt động - nó hoạt động nhanh - nhưng hơi khó để duy trì.

Một tùy chọn khác, có vòng quay OOP nhiều hơn, là sử dụng các trình xử lý sự kiện. Bạn tạo một mảng khóa-giá trị với các lệnh và các chức năng chuyên dụng của chúng. Khi một lệnh được đưa ra, bạn kiểm tra xem mảng có khóa đã cho không. Nếu có, gọi hàm. Đây sẽ là đề xuất của tôi cho mã mới.


Tôi đã đọc code của bạn và đó là chính xác những chương trình tương tự như mã của tôi, nhưng như tôi đã nói, nếu bạn muốn người khác sử dụng, bạn cần phải thêm kiểm tra lỗi và các công cụ
alfa64

1
@ alfa64 Vui lòng thêm bất kỳ làm rõ cho câu hỏi, thay vì ý kiến. Không rõ ràng chính xác những gì bạn đang yêu cầu, mặc dù có phần rõ ràng rằng bạn đang tìm kiếm một cái gì đó thực sự cụ thể. Nếu vậy, cho chúng tôi biết chính xác đó là gì. Tôi không nghĩ rằng nó rất dễ dàng để đi từ I think my implementation is very crude and faultyđể but as i stated, if you want other people to use, you need to add error checking and stuff... Hãy cho chúng tôi chính xác những gì là thô về nó và những gì bị lỗi, nó sẽ giúp bạn có được câu trả lời tốt hơn.
yannis

chắc chắn, tôi sẽ làm lại câu hỏi
alfa64

0

Tôi đề nghị sử dụng một công cụ, thay vì tự thực hiện một trình biên dịch hoặc trình thông dịch. Trớ trêu sử dụng C # để diễn đạt ngữ pháp ngôn ngữ đích (ngữ pháp của dòng lệnh của bạn). Mô tả trên CodePlex nói: "Irony là bộ công cụ phát triển để triển khai các ngôn ngữ trên nền tảng .NET.

Xem trang chủ chính thức của Irony trên CodePlex: Irony - Bộ triển khai ngôn ngữ .NET .


Làm thế nào bạn sẽ sử dụng nó với PHP?
SK-logic

Tôi không thấy bất kỳ thẻ PHP hoặc tham chiếu đến PHP trong câu hỏi.
Olivier Jacot-Descombes

Tôi thấy, nó từng là về PHP ban đầu, nhưng bây giờ được viết lại.
SK-logic

0

Lời khuyên của tôi sẽ là google cho một thư viện giải quyết vấn đề của bạn.

Gần đây tôi đã sử dụng NodeJS rất nhiều và Optimist là thứ tôi sử dụng để xử lý dòng lệnh. Tôi khuyến khích bạn tìm kiếm một ngôn ngữ bạn có thể sử dụng cho ngôn ngữ bạn chọn. Nếu không..viết một và mở mã nguồn: D Bạn thậm chí có thể đọc qua mã nguồn của Optimist và chuyển nó sang ngôn ngữ bạn chọn.


0

Tại sao bạn không đơn giản hóa một chút, yêu cầu của bạn?

Đừng sử dụng một trình phân tích cú pháp đầy đủ, nó quá phức tạp và thậm chí không cần thiết cho trường hợp của bạn.

Tạo một vòng lặp, viết một thông báo đại diện cho "dấu nhắc" của bạn, có thể là đường dẫn hiện tại của bạn.

Đợi một chuỗi, "phân tích" chuỗi và làm một cái gì đó tùy thuộc vào nội dung của chuỗi.

Chuỗi có thể "phân tích" như mong đợi một dòng, trong đó khoảng trắng là dấu phân cách ("mã thông báo") và phần còn lại của các ký tự được nhóm lại.

Thí dụ.

Các đầu ra chương trình (và nằm trong cùng một dòng): / user / files / Người dùng ghi (trong cùng một dòng) liệt kê tất cả;

Chương trình của bạn sẽ tạo ra một danh sách, bộ sưu tập hoặc mảng như

list

all;

hoặc nếu ";" được coi là một dấu phân cách như khoảng trắng

/user/files/

list

all

Chương trình của bạn có thể bắt đầu bằng cách mong đợi một hướng dẫn duy nhất, không có "đường ống" kiểu unix, không chuyển hướng kiểu windowze.

Chương trình của bạn có thể tạo một từ điển hướng dẫn, mỗi hướng dẫn, có thể có một danh sách các tham số.

Mẫu thiết kế lệnh áp dụng cho trường hợp của bạn:

http://en.wikipedia.org/wiki/Command_potype

Đây là một mã giả "đơn giản c", không được kiểm tra hoặc hoàn thành, chỉ là một ý tưởng về cách có thể được thực hiện.

Bạn cũng có thể làm cho nó hướng đối tượng hơn, và trong ngôn ngữ lập trình, bạn thích.

Thí dụ:


// "global function" pointer type declaration
typedef
  void (*ActionProc) ();

struct Command
{
  char[512] Identifier;
  ActionProc Action; 
};

// global var declarations

list<char*> CommandList = new list<char*>();
list<char*> Tokens = new list<char*>();

void Action_ListDirectory()
{
  // code to list directory
} // Action_ListDirectory()

void Action_ChangeDirectory()
{
  // code to change directory
} // Action_ChangeDirectory()

void Action_CreateDirectory()
{
  // code to create new directory
} // Action_CreateDirectory()

void PrepareCommandList()
{
  CommandList->Add("ls", &Action_ListDirectory);
  CommandList->Add("cd", &Action_ChangeDirectory);
  CommandList->Add("mkdir", &Action_CreateDirectory);

  // register more commands
} // void PrepareCommandList()

void interpret(char* args, int *ArgIndex)
{
  char* Separator = " ";
  Tokens = YourSeparateInTokensFunction(args, Separator);

  // "LocateCommand" may be case sensitive
  int AIndex = LocateCommand(CommandList, args[ArgIndex]);
  if (AIndex >= 0)
  {
    // the command

    move to the next parameter
    *ArgIndex = (*ArgIndex + 1);

    // obtain already registered command
    Command = CommandList[AIndex];

    // execute action
    Command.Action();
  }
  else
  {
    puts("some kind of command not found error, or, error syntax");
  }  
} // void interpret()

void main(...)
{
  bool CanContinue = false;
  char* Prompt = "c\:>";

  char Buffer[512];

  // which command line parameter string is been processed
  int ArgsIndex = 0;

  PrepareCommandList();

  do
  {
    // display "prompt"
    puts(Prompt);
    // wait for user input
      fgets(Buffer, sizeof(Buffer), stdin);

    interpret(buffer, &ArgsIndex);

  } while (CanContinue);

} // void main()

Bạn đã không đề cập đến ngôn ngữ lập trình của bạn. Bạn cũng có thể đề cập đến bất kỳ ngôn ngữ lập trình nào, nhưng tốt nhất là "XYZ".


0

bạn có một số nhiệm vụ trước bạn.

nhìn vào yêu cầu của bạn ...

  • Bạn cần phân tích lệnh. Đó là một nhiệm vụ khá dễ dàng
  • Bạn cần phải có một ngôn ngữ lệnh mở rộng.
  • Bạn cần phải kiểm tra lỗi và đề xuất.

Ngôn ngữ lệnh mở rộng chỉ ra rằng DSL là bắt buộc. Tôi khuyên bạn không nên tự lăn mà sử dụng JSON nếu các tiện ích mở rộng của bạn đơn giản. Nếu chúng phức tạp, một cú pháp biểu thức s là tốt.

Kiểm tra lỗi ngụ ý rằng hệ thống của bạn cũng biết về các lệnh có thể. Đó sẽ là một phần của hệ thống chỉ huy.

Nếu tôi đang triển khai một hệ thống như vậy từ đầu, tôi sẽ sử dụng Common Lisp với đầu đọc bị loại bỏ. Mỗi mã thông báo lệnh sẽ ánh xạ tới một ký hiệu, sẽ được chỉ định trong tệp RC biểu thức s. Sau khi mã thông báo, nó sẽ được đánh giá / mở rộng trong ngữ cảnh giới hạn, bẫy các lỗi và bất kỳ mẫu lỗi nào có thể nhận ra sẽ trả về các đề xuất. Sau đó, lệnh thực tế sẽ được gửi đến HĐH.


0

Có một tính năng hay trong lập trình chức năng mà bạn có thể quan tâm để xem xét.

Nó được gọi là khớp mẫu .

Dưới đây là hai liên kết cho một số ví dụ về khớp mẫu trong Scala và trong F # .

Tôi đồng ý với bạn rằng việc sử dụng các switchcấu trúc là một chút tẻ nhạt và tôi đặc biệt thích sử dụng kết hợp cha con trong quá trình thực hiện trình biên dịch trong Scala.

Cụ thể, tôi khuyên bạn nên xem xét ví dụ tính toán lambda của trang web Scala.

Theo tôi, đó là cách thông minh nhất để tiến hành, nhưng nếu bạn phải tuân thủ nghiêm ngặt PHP, thì bạn sẽ bị mắc kẹt với "trường học cũ" switch.


0

Hãy xem Apache CLI , toàn bộ mục đích của nó dường như đang thực hiện chính xác những gì bạn muốn làm, vì vậy ngay cả khi bạn không thể sử dụng nó, bạn có thể kiểm tra kiến ​​trúc của nó và sao chép nó.

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.