Có giải pháp thay thế cho flex / bison có thể sử dụng trên các hệ thống nhúng 8-bit không?


83

Tôi đang viết một trình thông dịch nhỏ cho một ngôn ngữ như BASIC đơn giản như một bài tập về vi điều khiển AVR trong C bằng chuỗi công cụ avr-gcc. Tuy nhiên, tôi tự hỏi liệu có bất kỳ công cụ mã nguồn mở nào có thể giúp tôi viết lexer và parser không.

Nếu tôi viết nó để chạy trên hộp Linux của mình, tôi có thể sử dụng flex / bison. Bây giờ tôi đã tự giới hạn mình trong nền tảng 8-bit, tôi phải làm tất cả bằng tay, hay không?


1
Có một con chip cụ thể mà bạn định sử dụng không? Nó có bao nhiêu ROM / RAM?
Steve S

Cập nhật vào liên kết của @mre. nhúng.com đã chuyển URL của họ vào thùng rác. ( Embedded.com/design/prototyping-and-development/4024523/... )
pgvoorhees

Dường như chỉ laguages stack (ra & Co) có cơ hội trên 2KB RAM, với hạt nhân lóe lên
Jacek Cz

Câu trả lời:


59

Tôi đã triển khai trình phân tích cú pháp cho một ngôn ngữ lệnh đơn giản được nhắm mục tiêu cho ATmega328p . Con chip này có ROM 32k và RAM chỉ 2k. RAM chắc chắn là hạn chế quan trọng hơn - nếu bạn chưa gắn liền với một con chip cụ thể, hãy chọn một con chip có càng nhiều RAM càng tốt. Điều này sẽ làm cho cuộc sống của bạn dễ dàng hơn nhiều.

Lúc đầu, tôi cân nhắc sử dụng flex / bison. Tôi quyết định từ chối lựa chọn này vì hai lý do chính:

  • Theo mặc định, Flex & Bison phụ thuộc vào một số chức năng thư viện tiêu chuẩn (đặc biệt cho I / O) không khả dụng hoặc không hoạt động giống nhau trong avr-libc. Tôi khá chắc chắn rằng có những cách giải quyết được hỗ trợ, nhưng đây là một số nỗ lực bổ sung mà bạn sẽ cần tính đến.
  • AVR có Kiến trúc Harvard . C không được thiết kế để giải thích điều này, vì vậy ngay cả các biến không đổi cũng được tải vào RAM theo mặc định . Bạn phải sử dụng macro / chức năng đặc biệt để lưu trữ và truy cập dữ liệu trong flashEEPROM . Flex & Bison tạo một số bảng tra cứu tương đối lớn và những bảng này sẽ ngốn RAM của bạn khá nhanh. Trừ khi tôi nhầm lẫn (điều này hoàn toàn có thể xảy ra), bạn sẽ phải chỉnh sửa nguồn đầu ra để tận dụng các giao diện Flash & EEPROM đặc biệt.

Sau khi từ chối Flex & Bison, tôi đã đi tìm các công cụ máy phát điện khác. Dưới đây là một số mà tôi đã xem xét:

Bạn cũng có thể muốn xem qua so sánh của Wikipedia .

Cuối cùng, tôi đã mã hóa bằng tay cả lexer và parser.

Để phân tích cú pháp, tôi đã sử dụng trình phân tích cú pháp gốc đệ quy. Tôi nghĩ Ira Baxter đã hoàn thành rất tốt chủ đề này, và có rất nhiều hướng dẫn trực tuyến.

Đối với lexer của mình, tôi đã viết các biểu thức chính quy cho tất cả các thiết bị đầu cuối của mình, lập sơ đồ máy trạng thái tương đương và triển khai nó như một hàm khổng lồ bằng cách sử dụng goto's để nhảy giữa các trạng thái. Điều này thật tẻ nhạt, nhưng kết quả làm việc tuyệt vời. Ngoài ra, gotolà một công cụ tuyệt vời để triển khai các máy trạng thái - tất cả các trạng thái của bạn có thể có nhãn rõ ràng ngay bên cạnh mã liên quan, không có lệnh gọi hàm hoặc chi phí biến trạng thái và nó nhanh như bạn có thể nhận được. C thực sự không có một cấu trúc tốt hơn để xây dựng các máy trạng thái tĩnh.

Điều cần suy nghĩ: lexers thực sự chỉ là một chuyên môn của trình phân tích cú pháp. Sự khác biệt lớn nhất là ngữ pháp thông thường thường đủ để phân tích từ vựng, trong khi hầu hết các ngôn ngữ lập trình có (chủ yếu) là ngữ pháp không có ngữ cảnh. Vì vậy, thực sự không có gì ngăn cản bạn triển khai lexer dưới dạng trình phân tích cú pháp gốc đệ quy hoặc sử dụng trình tạo phân tích cú pháp để viết lexer. Nó thường không thuận tiện bằng việc sử dụng một công cụ chuyên dụng hơn.


221

Nếu bạn muốn một cách dễ dàng để viết mã trình phân tích cú pháp hoặc bạn hạn chế về dung lượng, bạn nên viết mã thủ công trình phân tích cú pháp gốc đệ quy; về cơ bản đây là các trình phân tích cú pháp LL (1). Điều này đặc biệt hiệu quả đối với các ngôn ngữ "đơn giản" như Basic. (Tôi đã làm một vài trong số này vào những năm 70!). Tin tốt là chúng không chứa bất kỳ mã thư viện nào; chỉ những gì bạn viết.

Chúng khá dễ viết mã, nếu bạn đã có ngữ pháp. Đầu tiên, bạn phải loại bỏ các quy tắc đệ quy bên trái (ví dụ: X = XY). Điều này nói chung là khá dễ thực hiện, vì vậy tôi để nó như một bài tập. (Bạn không cần phải làm điều này đối với các quy tắc tạo danh sách; xem thảo luận bên dưới).

Sau đó, nếu bạn có quy tắc BNF của biểu mẫu:

 X = A B C ;

tạo một chương trình con cho mỗi mục trong quy tắc (X, A, B, C) trả về một boolean nói rằng "Tôi đã thấy cấu trúc cú pháp tương ứng". Đối với X, mã:

subroutine X()
     if ~(A()) return false;
     if ~(B()) { error(); return false; }
     if ~(C()) { error(); return false; }
     // insert semantic action here: generate code, do the work, ....
     return true;
end X;

Tương tự cho A, B, C.

Nếu mã thông báo là thiết bị đầu cuối, hãy viết mã kiểm tra luồng đầu vào cho chuỗi ký tự tạo nên thiết bị đầu cuối. Ví dụ: đối với một Số, hãy kiểm tra xem luồng đầu vào có chứa các chữ số hay không và đưa con trỏ luồng đầu vào qua các chữ số. Điều này đặc biệt dễ dàng nếu bạn đang phân tích cú pháp ra khỏi bộ đệm (đối với BASIC, bạn có xu hướng nhận được một dòng tại một thời điểm) bằng cách chỉ cần tiến hoặc không tiến một con trỏ quét bộ đệm. Mã này về cơ bản là phần lexer của trình phân tích cú pháp.

Nếu quy tắc BNF của bạn là đệ quy ... đừng lo lắng. Chỉ cần mã lệnh gọi đệ quy. Điều này xử lý các quy tắc ngữ pháp như:

T  =  '('  T  ')' ;

Điều này có thể được mã hóa là:

subroutine T()
     if ~(left_paren()) return false;
     if ~(T()) { error(); return false; }
     if ~(right_paren()) { error(); return false; }
     // insert semantic action here: generate code, do the work, ....
     return true;
end T;

Nếu bạn có quy tắc BNF với một quy tắc thay thế:

 P = Q | R ;

sau đó mã P với các lựa chọn thay thế:

subroutine P()
    if ~(Q())
        {if ~(R()) return false;
         return true;
        }
    return true;
end P;

Đôi khi bạn sẽ gặp các quy tắc tạo danh sách. Chúng có xu hướng là đệ quy trái và trường hợp này có thể dễ dàng xử lý. Ý tưởng cơ bản là sử dụng lặp thay vì đệ quy, và điều đó tránh đệ quy vô hạn mà bạn sẽ làm điều này theo cách "hiển nhiên". Thí dụ:

L  =  A |  L A ;

Bạn có thể viết mã này bằng cách sử dụng lặp lại như sau:

subroutine L()
    if ~(A()) then return false;
    while (A()) do { /* loop */ }
    return true;
end L;

Bạn có thể viết mã hàng trăm quy tắc ngữ pháp trong một hoặc hai ngày theo cách này. Có nhiều chi tiết hơn để điền vào, nhưng những điều cơ bản ở đây là quá đủ.

Nếu bạn thực sự eo hẹp về không gian, bạn có thể xây dựng một máy ảo thực hiện những ý tưởng này. Đó là những gì tôi đã làm vào những năm 70, khi 8K từ 16 bit là những gì bạn có thể nhận được.


Nếu bạn không muốn viết mã này bằng tay, bạn có thể tự động hóa nó bằng một siêu trình biên dịch ( Meta II ) về cơ bản tạo ra cùng một thứ. Đây là những điều thú vị về kỹ thuật và thực sự giúp bạn thực hiện điều này, ngay cả đối với những nhà ngữ pháp lớn.

Tháng 8 năm 2014:

Tôi nhận được rất nhiều yêu cầu về "cách tạo AST bằng trình phân tích cú pháp". Để biết thông tin chi tiết về vấn đề này, về cơ bản tạo ra câu trả lời này, hãy xem câu trả lời SO khác của tôi https://stackoverflow.com/a/25106688/120163

Tháng 7 năm 2015:

Có rất nhiều người muốn viết một trình đánh giá biểu thức đơn giản. Bạn có thể làm điều này bằng cách thực hiện những việc tương tự như liên kết "Trình tạo AST" ở trên gợi ý; chỉ cần làm số học thay vì xây dựng các nút cây. Đây là một trình đánh giá biểu thức được thực hiện theo cách này .


2
Vâng, không quá khó để cuộn một trình phân tích cú pháp gốc đệ quy bằng tay cho một ngôn ngữ đơn giản. Hãy nhớ tối ưu hóa các lệnh gọi đuôi khi bạn có thể - không gian ngăn xếp quan trọng rất nhiều khi bạn chỉ có một vài kilobyte RAM.
Steve S

2
Tất cả: có, bạn có thể thực hiện tối ưu hóa cuộc gọi đuôi. Điều này sẽ không thành vấn đề trừ khi bạn mong đợi việc lồng vào mã đã phân tích cú pháp của mình để thực sự sâu; đối với một dòng mã BASIC, nó khá khó để tìm thấy các biểu thức sâu hơn 10 thấu kính và bạn luôn có thể đặt số lượng giới hạn độ sâu để khởi động. Đúng là các hệ thống nhúng có xu hướng có ít không gian ngăn xếp hơn, vì vậy ít nhất hãy chú ý đến sự lựa chọn của bạn ở đây.
Ira Baxter

2
@Mark: và nó có thể là năm 2012, nhưng tài liệu kỹ thuật năm 1965 mà tôi tham khảo chỉ là tốt như bây giờ và nó khá tốt, đặc biệt nếu bạn không biết nó.
Ira Baxter

2
@Mark, ah, ok, cảm ơn! Có vẻ như ngày đã được ấn định một cách bí ẩn. Cảm ơn, Chúa tể thời gian.
Ira Baxter

2
Làm cách nào để xử lý các chuỗi trống?
Dante

11

Bạn có thể sử dụng flex / bison trên Linux với gcc gốc của nó để tạo mã mà sau đó bạn sẽ biên dịch chéo với gcc AVR của mình cho mục tiêu được nhúng.


2

GCC có thể biên dịch chéo sang nhiều nền tảng khác nhau, nhưng bạn chạy flex và bison trên nền tảng mà bạn đang chạy trình biên dịch. Họ chỉ lấy ra mã C mà trình biên dịch sau đó xây dựng. Kiểm tra nó để xem tệp thực thi kết quả thực sự lớn như thế nào. Lưu ý rằng chúng có các thư viện thời gian chạy ( libfl.av.v.) mà bạn cũng sẽ phải biên dịch chéo tới mục tiêu của mình.


Tôi vẫn phải điều tra quy mô của những thư viện đó và đó là lý do tại sao tôi đặt câu hỏi ngay từ đầu. Tôi muốn một cái gì đó được nhắm mục tiêu cụ thể đến các MCU nhỏ.
Johan

-1

Hãy thử Boost :: Spirit. Đó là một thư viện chỉ dành cho tiêu đề mà bạn có thể đăng nhập và xây dựng một trình phân tích cú pháp rất nhanh, sạch hoàn toàn bằng C ++. Các toán tử được nạp chồng trong C ++ được sử dụng thay vì một tệp ngữ pháp đặc biệt.


Vấn đề với câu trả lời của bạn là nó không nhận biết được ràng buộc nền tảng 8-bit. Sẽ rất khó để có được một chuỗi công cụ hỗ trợ và một nền tảng nhỏ như vậy cùng một lúc.
Waslap

-5

Thay vì phát minh lại bánh xe, hãy xem LUA: www.lua.org . Nó là một ngôn ngữ thông dịch có nghĩa là được nhúng trong phần mềm khác và được sử dụng trên các hệ thống quy mô nhỏ, chẳng hạn như hệ thống nhúng. Cây phân tích cú pháp thủ tục tích hợp sẵn, logic điều khiển, toán học và hỗ trợ biến - không cần phải phát minh lại thứ gì đó mà hàng nghìn người khác đã gỡ lỗi và sử dụng. Và nó có thể mở rộng, nghĩa là bạn có thể thêm vào ngữ pháp bằng cách thêm các hàm C của riêng bạn.


2
Có thể nhỏ như Lua, tôi khá chắc rằng nó vẫn còn quá lớn.
icktoofay
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.