2010-09-23 35 views
5

Đối với khuôn khổ chút phân tích cú pháp rất riêng của tôi, tôi đang cố gắng để xác định (một cái gì đó tương tự) các chức năng sau:"bản sao cacbon" a c + + istream?

template <class T> 
// with operator>>(std::istream&, T&) 
void tryParse(std::istream& is, T& tgt) 
{ 
    is >> tgt /* , *BUT* store every character that is consumed by this operation 
    in some string. If afterwards, is.fail() (which should indicate a parsing 
    error for now), put all the characters read back into the 'is' stream so that 
    we can try a different parser. */ 
} 

Sau đó, tôi có thể viết một cái gì đó như thế này: (có thể không phải là ví dụ tốt nhất)

/* grammar: MyData  = <IntTriple> | <DoublePair> 
      DoublePair = <double> <double> 
      IntTriple = <int> <int> <int> */ 
class MyData 
{ public: 
    union { DoublePair dp; IntTriple it; } data; 
    bool isDoublePair; 
}; 

istream& operator>>(istream& is, MyData& md) 
{ 
    /* If I used just "is >> md.data.it" here instead, the 
     operator>>(..., IntTriple) might consume two ints, then hit an 
     unexpected character, and fail, making it impossible to read these two 
     numbers as doubles in the "else" branch below. */ 
    tryParse(is, md.data.it); 
    if (!is.fail()) 
     md.isDoublePair = false; 
    else 
    { 
     md.isDoublePair = true; 
     is.clear(); 
     is >> md.data.dp; 
    } 
    return is; 
} 

Bất kỳ trợ giúp nào được đánh giá cao.

+0

Luồng không phải là công cụ phù hợp cho điều này, do thiếu sự bỏ rơi phù hợp. Khi thiết kế các trình phân tích cú pháp nội tuyến đơn giản như thế này (nếu không, hãy cố gắng để 'tăng :: tinh thần'), các hàm phân tích cú pháp thực sự cần có một cặp các trình lặp. Nó trở nên dễ dàng để quay trở lại (chỉ cần lưu giá trị của trình lặp trước một trình phân tích cú pháp backtracking). –

Trả lời

3

Thật không may, các luồng chỉ có hỗ trợ bỏ qua rất nhỏ và thô sơ.

Lần cuối cùng tôi cần điều này, tôi đã viết các lớp đọc của riêng mình bao gồm một luồng, nhưng có một bộ đệm để đưa mọi thứ trở lại và đọc từ luồng chỉ khi bộ đệm trống. Chúng có cách để có được một trạng thái từ, và bạn có thể cam kết một trạng thái hoặc rollback đến một trạng thái trước đó.
Hành động mặc định trong destructor của lớp nhà nước là quay ngược lại, để bạn có thể phân tích trước mà không đưa ra nhiều suy nghĩ về xử lý lỗi, vì ngoại lệ sẽ đơn giản khôi phục trạng thái của trình phân tích tới một điểm mà quy tắc ngữ pháp khác đã được thử. (Tôi nghĩ rằng điều này được gọi là backtracking.) Dưới đây là một phác thảo:

class parse_buffer { 
    friend class parse_state; 
public: 
    typedef std::string::size_type index_type; 

    parse_buffer(std::istream& str); 

    index_type get_current_index() const; 
    void set_current_index(index_type) const; 

    std::string get_next_string(bool skip_ws = true) const; 
    char get_next_char(bool skip_ws = true); 
    char peek_next_char(bool skip_ws = true); 

    std::string get_error_string() const; // returns string starting at error idx 
    index_type get_error_index() const; 
    void set_error_index(index_type); 

    bool eof() const; 

    // ... 
}; 

class parse_state { 
public: 
    parse_state(parse_buffer&); 
    ~parse_state(); 

    void commit(); 
    void rollback(); 

    // ... 
}; 

Điều này sẽ cung cấp cho bạn một ý tưởng. Nó không có triển khai thực hiện, nhưng điều đó rất đơn giản và dễ làm lại. Ngoài ra, mã thực có nhiều chức năng thuận tiện như đọc các hàm đọc chuỗi phân cách, tiêu thụ chuỗi nếu nó là một trong một số từ khóa nhất định, đọc chuỗi và chuyển đổi thành loại được cho theo thông số mẫu và nội dung như thế này.

Ý tưởng là một hàm sẽ đặt chỉ mục lỗi thành vị trí bắt đầu, lưu trạng thái phân tích cú pháp và cố gắng phân tích cú pháp cho đến khi thành công hoặc chạy thành một kết thúc chết. Trong trường hợp thứ hai, nó sẽ chỉ ném một ngoại lệ. Điều này sẽ phá hủy các đối tượng parse_state trên ngăn xếp, cuộn trở lại trạng thái lên đến một chức năng có thể bắt ngoại lệ và thử cái gì khác, hoặc xuất ra một lỗi (đó là nơi get_error_string().)

Nếu bạn muốn trình phân tích cú pháp thực sự nhanh, chiến lược này có thể sai, nhưng sau đó luồng cũng thường chậm. OTOH, lần cuối cùng tôi sử dụng một cái gì đó như thế này, tôi đã thực hiện một trình phân tích cú pháp XPath hoạt động trên một DOM độc quyền, được sử dụng để biểu diễn các cảnh trong trình kết xuất 3D.Và đó là không phải là trình phân tích cú pháp XPath có tất cả sức nóng từ những kẻ cố gắng đạt được tốc độ khung hình cao hơn. :)

+0

Điều đó nghe khá thú vị. Bạn vẫn có mã và nó có phải là mã nguồn mở/tôi có thể xem nó không? – rainmaker

+0

Ồ, thật tuyệt! Vì vậy, parse_state :: rollback() chỉ cần gọi set_current_index (index_on_my_creation) trên parse_buffer của nó? và commit() không - uhm - không có gì, để lại chỉ mục của parse_buffer ở đâu? Ah, nhưng commit() có thể nói parse_buffer rằng chúng ta sẽ không rollback() trước vị trí hiện tại và do đó nó an toàn (ít nhất là cho parse_state này) để quên mọi thứ trước vị trí đó ... Vâng, tôi nghĩ tôi bắt đầu hiểu. Tôi sẽ thử điều này, cảm ơn rất nhiều! – rainmaker

+0

@rainmaker: Vâng, bạn có ý tưởng. Một điều nữa cam kết là đặt chỉ mục lỗi cho vị trí hiện tại, để các lỗi phân tích cú pháp sau này sẽ mang lại chỉ mục hiện tại làm nơi văn bản bị lỗi bắt đầu. Tôi mơ hồ nhớ rằng điều này có nooks và crannies của nó (sẽ không 'parse_state' cũng phải lưu trữ chỉ số lỗi cũ?), Nhưng một vài năm trước đây tôi đã sử dụng lược đồ này, vì vậy, không có mã cũ, tôi sẽ cần phải làm điều này một lần nữa, tôi sẽ không có nhiều hơn ở trên để bắt đầu từ một trong hai. ':)' – sbi

3

Đây không phải là luồng dành cho. Bạn nên đọc dữ liệu bạn muốn phân tích cú pháp thành một bộ đệm và sau đó trao bộ đệm đó (tốt nhất là một dải vòng lặp) cho các hàm phân tích nó. Điều này có thể giống như thế này:

template <class T, class U> 
bool tryParse(U & begin, U & end, T & target) { 
    // return true if parse was successful, false otherwise 
} 

Để đọc từ một istream vào một bộ đệm, bạn có thể sử dụng một istream_iterator:

std::vector<char> buffer(std::istream_iterator<char>(is), std::istream_iterator<char>()); 

này đọc toàn bộ dòng vào vector khi nó được tạo ra.

2

Đặt các ký tự trở lại là khó khăn. Một số luồng hỗ trợ unget()putback(somechar), nhưng không đảm bảo số lượng ký tự bạn có thể vô hiệu hóa (nếu có).

Cách đáng tin cậy hơn là đọc các ký tự vào bộ đệm và phân tích cú pháp đó hoặc lưu trữ các ký tự được đọc trong lần phân tích cú pháp đầu tiên và sử dụng bộ đệm đó khi phân tích cú pháp lần thứ hai.

+0

+1 để phân tích cú pháp bộ đệm riêng biệt. –

1

Bạn có thể thực hiện một số điều thú vị với streambuf thành viên luồng. Đặc biệt, bạn có quyền truy cập trực tiếp vào các con trỏ của bộ đệm.

Tuy nhiên, bạn không đảm bảo về kích thước bộ đệm.

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