2009-02-07 30 views
33

Trong ứng dụng Python của tôi, tôi cần viết biểu thức chính quy khớp với vòng lặp C++ for hoặc while đã kết thúc bằng dấu chấm phẩy (;). Ví dụ, nó phải phù hợp này:Biểu thức chính quy để phát hiện dấu chấm phẩy kết thúc C++ cho & trong khi vòng

for (int i = 0; i < 10; i++); 

... nhưng không này:

for (int i = 0; i < 10; i++) 

này trông tầm thường ở cái nhìn đầu tiên, cho đến khi bạn nhận ra rằng văn bản giữa việc mở và ngoặc đóng có thể chứa dấu ngoặc đơn khác, ví dụ:

for (int i = funcA(); i < funcB(); i++); 

Tôi đang sử dụng mô đun python.re. Ngay bây giờ biểu hiện thường xuyên của tôi trông như thế này (tôi đã để lại những comment của tôi trong để bạn có thể hiểu dễ dàng hơn):

# match any line that begins with a "for" or "while" statement: 
^\s*(for|while)\s* 
\( # match the initial opening parenthesis 
    # Now make a named group 'balanced' which matches a balanced substring. 
    (?P<balanced> 
     # A balanced substring is either something that is not a parenthesis: 
     [^()] 
     | # …or a parenthesised string: 
     \(# A parenthesised string begins with an opening parenthesis 
      (?P=balanced)* # …followed by a sequence of balanced substrings 
     \) # …and ends with a closing parenthesis 
    )* # Look for a sequence of balanced substrings 
\) # Finally, the outer closing parenthesis. 
# must end with a semi-colon to match: 
\s*;\s* 

này hoạt động hoàn hảo cho tất cả các trường hợp trên, nhưng nó phá vỡ ngay khi bạn cố gắng và làm phần thứ ba của vòng lặp for chứa một hàm, giống như vậy:

for (int i = 0; i < 10; doSomethingTo(i)); 

Tôi nghĩ rằng nó vỡ vì ngay khi bạn đặt một số văn bản giữa dấu ngoặc đơn mở và đóng, nhóm "cân bằng" khớp với văn bản có chứa, và do đó phần (?P=balanced) không hoạt động nữa vì nó sẽ không khớp (do thực tế là văn bản bên trong dấu ngoặc đơn là khác nhau).

Trong mã Python của tôi Tôi đang sử dụng cờ VERBOSE và MULTILINE, và tạo ra các biểu thức chính quy như sau:

REGEX_STR = r"""# match any line that begins with a "for" or "while" statement: 
^\s*(for|while)\s* 
\( # match the initial opening parenthesis 
    # Now make a named group 'balanced' which matches 
    # a balanced substring. 
    (?P<balanced> 
     # A balanced substring is either something that is not a parenthesis: 
     [^()] 
     | # …or a parenthesised string: 
     \(# A parenthesised string begins with an opening parenthesis 
      (?P=balanced)* # …followed by a sequence of balanced substrings 
     \) # …and ends with a closing parenthesis 
    )* # Look for a sequence of balanced substrings 
\) # Finally, the outer closing parenthesis. 
# must end with a semi-colon to match: 
\s*;\s*""" 

REGEX_OBJ = re.compile(REGEX_STR, re.MULTILINE| re.VERBOSE) 

bất cứ ai có thể đề xuất một sự cải tiến để biểu thức chính quy này? Nó trở nên quá phức tạp đối với tôi để có được đầu của tôi xung quanh.

Trả lời

97

Bạn có thể viết một chút, thói quen rất đơn giản nào đó, mà không sử dụng một biểu thức chính quy:

  • Đặt một vị trí truy cập pos cho nên đó là điểm để chỉ trước ngày khai mạc đặt sau for hoặc while của bạn.
  • Đặt bộ đếm ngoặc mở openBr thành 0.
  • Bây giờ, hãy tiếp tục tăng thêm pos, đọc các ký tự tại các vị trí tương ứng và tăng openBr khi bạn nhìn thấy dấu ngoặc mở và giảm nó khi bạn thấy một dấu đóng. Điều đó sẽ tăng nó một lần ở đầu, cho khung mở đầu tiên trong "for (", tăng và giảm một số chi tiết cho một số dấu ngoặc ở giữa và đặt lại thành 0 khi đóng khung for của bạn.
  • Vì vậy, hãy dừng lại khi openBr0 một lần nữa.

Vị trí dừng là khung đóng của bạn là for(...). Bây giờ bạn có thể kiểm tra xem có dấu chấm phẩy sau hay không.

+0

Cảm ơn - Tôi đoán biểu thức chính quy thực sự là công cụ sai cho công việc! – Thomi

+10

Bạn cũng cần tính đến các chú thích và chuỗi tài khoản, cả hai đều sẽ giải thích thuật toán này. –

+2

Bạn có thể xóa nhận xét và chuỗi trước bằng cụm từ thông dụng. :) Hoặc giới thiệu nhiều biến hơn như openBr, cho biết nếu bạn đang ở trong một nhận xét (và loại nhận xét nào, vì vậy bạn biết ký tự nào đóng nó) hoặc một chuỗi. – Frank

20

Đây là loại điều bạn thực sự không nên làm với cụm từ thông dụng. Chỉ cần phân tích cú pháp chuỗi một ký tự cùng một lúc, theo dõi mở/đóng dấu ngoặc đơn.

Nếu đây là tất cả những gì bạn đang tìm kiếm, bạn chắc chắn không cần một trình soạn thảo ngữ pháp/phân tích ngữ pháp C++ đầy đủ. Nếu bạn muốn thực hành, bạn có thể viết một trình phân tích cú pháp đệ quy nhỏ, nhưng thậm chí nó hơi nhiều cho các dấu ngoặc đơn phù hợp.

+0

Thực ra, với tăng: xpressive và có thể là python, bạn có thể có regexp thực hiện đối sánh paren cân bằng. –

8

Đây là ví dụ tuyệt vời về việc sử dụng công cụ sai cho công việc. Cụm từ thông dụng không xử lý các kết quả phụ được lồng ghép tùy ý rất tốt. Những gì bạn nên làm thay vì sử dụng một lexer thực và phân tích cú pháp (một ngữ pháp cho C + + nên dễ tìm) và tìm các cơ quan vòng lặp rỗng bất ngờ.

+1

+1, Nói đúng ra, regex không xử lý các biểu thức lồng nhau. Các biểu thức chính quy xử lý các biểu thức lồng nhau đã vượt quá thành ngữ pháp ngữ miễn phí. – JaredPar

+0

Tôi đồng ý với việc sử dụng flex/yacc hoặc tương tự. Nhưng ngữ pháp C++ có thực sự dễ tìm không? Có ai có một liên kết? Tôi nhớ những người từ CDT/Eclipse đã khó phân tích cú pháp đầu vào C++ một cách chính xác và nhanh chóng. – Frank

+0

Có lẽ không; C++ dĩ nhiên rất khó phân tích cú pháp. Vì câu hỏi ban đầu không yêu cầu phân tích đầy đủ ngữ nghĩa của nguồn đầu vào, một trình phân tích cú pháp đơn giản hơn, không đầy đủ có thể cũng có thể thực hiện công việc đó. –

2

Tôi thậm chí sẽ không chú ý đến nội dung của các parens.

Chỉ cần phù hợp với bất kỳ dòng bắt đầu với for và kết thúc với dấu chấm phẩy:

^\t*for.+;$ 

Trừ khi bạn đã có for tuyên bố chia trên nhiều dòng, mà sẽ làm việc tốt?

+0

Điều đó có lẽ không đủ bởi vì mọi người phân chia các câu lệnh() trên nhiều dòng. – Frank

+0

dehmann là chính xác - ý tưởng là mẫu khớp với các ví dụ từ một mã cơ sở thực, do đó, nó phải có khả năng xử lý tất cả các giá trị hợp lệ cho cấu trúc vòng lặp, bao gồm cả nhiều dòng. – Thomi

1

Greg là hoàn toàn chính xác. Loại phân tích cú pháp này không thể được thực hiện với các biểu thức chính quy. Tôi cho rằng nó có thể xây dựng một số quái dị khủng khiếp mà sẽ làm việc cho nhiều trường hợp, nhưng sau đó bạn sẽ chỉ chạy trên một cái gì đó mà không.

Bạn thực sự cần phải sử dụng các kỹ thuật phân tích cú pháp truyền thống hơn. Ví dụ, nó khá đơn giản để viết một trình phân tích cú pháp khá đệ quy để làm những gì bạn cần.

1

Tôi không biết rằng regex sẽ xử lý một cái gì đó như thế rất tốt. Hãy thử một cái gì đó như thế này

line = line.Trim(); 
if(line.StartsWith("for") && line.EndsWith(";")){ 
    //your code here 
} 
+0

+1. Tất nhiên chúng ta đang nói Python ở đây vì vậy cú pháp là khác nhau nhỏ. Nhưng nếu bạn không thực sự phân tích cú pháp C đúng cách, không có lý do gì để tìm kiếm bất kỳ điều gì khác ngoài ‘);’ ở cuối dòng ‘for’. – bobince

1

Một ý nghĩ rằng bỏ qua dấu ngoặc đơn và xử lý các for như một cấu trúc tổ chức ba giá trị dấu chấm phẩy được phân định:

for\s*\([^;]+;[^;]+;[^;]+\)\s*; 

tùy chọn này hoạt động ngay cả khi chia trên nhiều dòng (một lần MULTILINE kích hoạt), nhưng giả định rằng for (... ; ... ; ...) là cấu trúc hợp lệ duy nhất, vì vậy sẽ không hoạt động với cấu trúc for (x in y) hoặc các độ lệch khác.

Cũng giả định rằng không có chức năng chứa dấu chấm phẩy như các đối số, chẳng hạn như:

for (var i = 0; i < ListLen('a;b;c',';') ; i++); 

Cho dù đây là một trường hợp có thể phụ thuộc vào những gì bạn đang thực sự làm điều này cho.

2

Hãy thử regexp này

^\s*(for|while)\s* 
\(
(?P<balanced> 
[^()]* 
| 
(?P=balanced) 
\) 
\s*;\s 

tôi loại bỏ các gói \(\) xung quanh (?P=balanced) và chuyển * để đằng sau bất kỳ chuỗi không paren. Tôi đã có công việc này với tăng xpressive, và kiểm tra lại trang web đó (Xpressive) để làm mới bộ nhớ của tôi.

0

Như Frank đã đề xuất, điều này là tốt nhất mà không có regex.Dưới đây là (một xấu xí) one-liner:

match_string = orig_string[orig_string.index("("):len(orig_string)-orig_string[::-1].index(")")] 

Phù hợp với dòng troll est nêu trong bình luận của ông:

orig_string = "for (int i = 0; i < 10; doSomethingTo(\"(\"));" 
match_string = orig_string[orig_string.index("("):len(orig_string)-orig_string[::-1].index(")")] 

lợi nhuận (int i = 0; i < 10; doSomethingTo("("))

này hoạt động bằng cách chạy qua chuỗi phía trước cho đến khi nó đạt đến paren mở đầu tiên, và sau đó lùi lại cho đến khi nó đạt đến dấu ngoặc đóng đầu tiên. Sau đó, nó sử dụng hai chỉ mục này để cắt chuỗi.

Các vấn đề liên quan