- vừa được xem lúc

Viết Reminder Parser dùng Rust

0 0 16

Người đăng: Huy Tran

Theo The Full Snack

<- Quay về trang chủ

Viết Reminder Parser dùng Rust

Khi dùng Google Calendar hoặc Reminder.app của macOS, mình rất thích chức năng tạo nhanh một event bằng cách nhập vào nội dung một cách tự nhiên như khi đang nói, ví dụ:

get hair cut at 10am every Sunday

hoặc là

doctor appointment at 1pm on Monday

Khi nhận được input như này, một event mới sẽ được tạo ra với ngày và giờ tương ứng, còn nội dung của event sẽ là phần text mở đầu, ví dụ "get hair cut", hoặc "doctor appointment".

Thường thì cái gì mình thích, mình sẽ tìm cách clone lại, chức năng này cũng ko ngoại lệ.

Vì bài viết này là phần tiếp theo của bài viết trước, chúng ta sẽ tiếp tục dùng Rust và Nom. Recommend các bạn đọc kĩ phần trước, và chuẩn bị những kiến thức cơ bản của Rust, nhất là về Result, Option, cargo test, trước khi đọc tiếp bài này.

Các bạn cũng có thể tham khảo code implementation hoàn chỉnh tại Github trước khi bắt đầu: https://github.com/huytd/reminder-parser

Phân tích cú pháp

Đây không phải là cú pháp mà Google Calendar hay Reminder.app sử dụng, nhưng đây sẽ là cú pháp chúng ta sẽ dùng trong bài viết này, đơn giản là vì nó... đơn giản

Cấu trúc của một event input là một tổ hợp của nhiều token như sau:

" — Bình luận từ đồng nghiệp của tác giả Đây là một thói quen tốt, các bạn cũng nên làm như vậy, tốn time chút nhưng hiệu quả.

#[test]
fn test_parse_time() { let test_times = [ ("at 11:00", Ok(("11", "00", true))), ("at 10pm", Ok(("10", "00", false))), ("at 12:13 am", Ok(("12", "13", true))), ("13:42pm", Ok(("13", "42", false))), ("15:30", Ok(("15", "30", true))), ("at 5", Ok(("5", "00", true))), ("32:412", Ok(("32", "412", true))), ("at 32:281am", Ok(("32", "281", true))), ("at 32pm", Ok(("32", "00", false))), ("night time", Err(())), ("at night", Err(())), ]; for test_case in test_times { let result = parse_time(test_case.0); if test_case.1.is_ok() { let (_, actual) = result.unwrap(); let expected = test_case.1.unwrap(); assert_eq!(actual.hour, expected.0); assert_eq!(actual.minute, expected.1); assert_eq!(actual.meridiem, expected.2); } else { assert!(result.is_err()); } }
}

Trong hàm test_parse_time() ở trên, ta tạo một mảng test_times chứa tập các tuple dạng (input, expected_result). Trong đó, các input là các string mà trên thực tế sẽ được nhập vào từ phía user, expected_result là một giá trị kiểu Result, nếu nó là giá trị Ok(...) có nghĩa là input này parse được, còn giá trị Err() là trường hợp lỗi. Mỗi giá trị Ok(...) sẽ có dạng (hour, minute, meridiem). Trong vòng lặp tiếp theo của hàm test, ta duyệt qua từng test case, lấy ra giá trị thực tế actual từ parser (kiểu ReminderTime) và so sánh nó với từng giá trị trong tuple test.

Phương pháp này không có gì mới, bên Golang gọi là table driven testing.

Có test rồi thì ta có thể implement được rồi, quay lại hàm parse_time, việc đầu tiên là dùng các combinator của nom để đọc input, nhắc lại cú pháp của <time> token:

time = ?"at" + hour + ?(":" + minutes) + ?("am"|"pm")

Trước khi đi vào giải thích, đây là hàm parse_time hoàn chỉnh:

fn parse_time(input: &str) -> IResult<&str, ReminderTime> { let (remain, (_, _, hour, opt_min, _, am)) = tuple(( opt(tag("at")), multispace0, digit1, opt(tuple((tag(":"), digit1))), multispace0, opt(alt((tag("am"), tag("pm")))), )) .parse(input)?; let (_, minute) = opt_min.unwrap_or(("", "00")); let meridiem = am == Some("am") || am == None; Ok((remain, ReminderTime { hour, minute, meridiem }))
}

Implement đầy đủ của hàm parse_date như sau:

fn parse_date(input: &str) -> IResult<&str, ReminderDate> { let (remain, (_, opt_repeat, _, date)) = tuple(( multispace0, opt(alt((value(true, tag("every")), value(false, tag("on"))))), multispace0, rest )) .parse(input)?; let repeated = opt_repeat.unwrap_or(false); let content = if date.trim().is_empty() { "today" } else { date.trim() }; Ok((remain, ReminderDate { content, repeated }))
}

Như đã viết ở trên, cú pháp của <date> token là:

date = ?("on"|"every") + date

và hy vọng bài viết này giúp các bạn hiểu rõ hơn về Nom Parser, cũng như kĩ thuật Parser Combination. Lần tới, khi cần parse một nội dung gì đó, thay vì đâm đầu vào sử dụng RegEx, hãy thử chậm lại một tí và sử dụng Nom hay một thư viện Parser Combination nào đó để build, mình nghĩ ngoài đồng nghiệp ra, thì chính bạn trong tương lai sẽ rất cảm kích cái quyết định đó của bản thân. Chúc các bạn may mắn

Hẹn gặp lại các bạn trong các bài viết tiếp theo.

Bình luận

Bài viết tương tự

- vừa được xem lúc

Open Source Story: Agar.IO Clone

Open Source Story: Agar.IO Clone.

0 0 25

- vừa được xem lúc

Chuyện cái comment

Chuyện cái comment. Chuyện rằng, có một ông bạn nọ có cái blog ở trên mạng, cũng có dăm.

0 0 19

- vừa được xem lúc

Quick and Dirty Stack, Queue and Deque in JavaScript

Quick and Dirty Stack, Queue and Deque in JavaScript. Trong quá trình phỏng vấn, dùng JavaScript, nếu đề bài không yêu cầu bắt buộc phải implement Stack hoặc Queue thì chúng ta có thể tiết kiệm thời g

0 0 14

- vừa được xem lúc

Algorithm in Frontend - Kỳ 3: Hashmap

Algorithm in Frontend - Kỳ 3: Hashmap. Hôm nay nói về một ứng dụng của Hashmap trong việc optimize một số thuật toán thường gặp trên Frontend.

0 0 20

- vừa được xem lúc

Game of Life

Game of Life. Game of Life của Conway là một trò mô phỏng khá là nổi tiếng.

0 0 65

- vừa được xem lúc

Algorithm Visualization

Algorithm Visualization. Algorithm Visualization là kĩ thuật hình tượng hóa quá trình hoạt động của một thuật toán, chúng ta thường thực hiện nó bằng nhiều cách khác nhau như: viết, vẽ, lập bảng giá t

0 0 22