Menu
CoddyTech

Ôn tập: trình phân tích tệp

Thuộc phần Lập trình hướng đối tượng trong hành trình GO của Coddy. Bài học 58 trên 107.

challenge icon

Thử thách

Dễ

Hãy xây dựng một trình phân tích cú pháp tệp cấu hình kết hợp tất cả các kỹ thuật xử lý lỗi trong chương này. Trình phân tích cú pháp sẽ xử lý nhiều tình huống lỗi bằng các kiểu lỗi tùy chỉnh, lỗi sentinel, bọc lỗi và dọn dẹp phù hợp bằng defer.

Bạn sẽ sắp xếp code trong ba tệp:

  • errors.go: Định nghĩa cơ sở hạ tầng lỗi cho trình phân tích cú pháp.

    Tạo hai lỗi sentinel:

    • ErrEmptyContent với thông báo "file content is empty"
    • ErrInvalidFormat với thông báo "invalid configuration format"

    Tạo kiểu lỗi tùy chỉnh ParseError với các trường Line (int) và Message (string). Phương thức Error() của kiểu này sẽ trả về: parse error at line [Line]: [Message]

  • parser.go: Xây dựng trình phân tích cú pháp cấu hình với cách xử lý lỗi theo từng lớp.

    Tạo một struct ConfigParser với trường boolean parsed để theo dõi việc phân tích cú pháp đã hoàn tất thành công hay chưa.

    Triển khai các phương thức sau:

    • NewConfigParser() *ConfigParser: tạo trình phân tích cú pháp mới với parsed được đặt thành false
    • ParseLine(lineNum int, line string) error: kiểm tra một dòng. Nếu dòng không chứa ký tự =, trả về *ParseError với số dòng và thông báo "missing key-value separator". Nếu không, trả về nil.
    • Parse(content string) error: phương thức phân tích cú pháp chính, thực hiện các việc sau:
      • Trả về ErrEmptyContent nếu nội dung trống
      • Trả về ErrInvalidFormat nếu nội dung không chứa ít nhất một ký tự xuống dòng
      • Tách nội dung theo ký tự xuống dòng và gọi ParseLine cho từng dòng, số dòng bắt đầu từ 1
      • Nếu ParseLine trả về lỗi, bọc lỗi đó bằng "parsing configuration: %w" rồi trả về
      • Đặt parsed thành true khi thành công và trả về nil
    • Status() string: trả về "ready" nếu parsed là true, nếu không thì trả về "not parsed"
  • main.go: Đọc nội dung cấu hình và minh họa cách kiểm tra lỗi toàn diện.

    Đọc hai dòng đầu vào tạo thành nội dung cấu hình khi ghép lại bằng một ký tự xuống dòng. Dùng defer để luôn in trạng thái của trình phân tích cú pháp ở cuối, bất kể thành công hay thất bại.

    Sau khi thử phân tích cú pháp, kiểm tra mọi lỗi bằng cả errors.Is() và errors.As() để đưa ra phản hồi cụ thể tùy theo lỗi xảy ra.

Các đầu vào sau sẽ được cung cấp:

  • Dòng 1: Dòng đầu tiên của nội dung cấu hình
  • Dòng 2: Dòng thứ hai của nội dung cấu hình

Xử lý lỗi bằng các thông báo cụ thể:

  • Nếu errors.Is() tìm thấy ErrEmptyContent: in Error: No configuration provided
  • Nếu errors.Is() tìm thấy ErrInvalidFormat: in Error: Configuration must have multiple lines
  • Nếu errors.As() tìm thấy một *ParseError: in toàn bộ lỗi đã bọc, sau đó xuống dòng và in Fix line [Line]: [Message]
  • Nếu phân tích cú pháp thành công: in Configuration parsed successfully

Sau khi xử lý kết quả, dù thành công hay gặp lỗi, trạng thái được in bằng defer sẽ xuất hiện trên dòng mới: Parser status: [status]

Ví dụ, với host=localhost và port=8080, đầu ra sẽ là:

Configuration parsed successfully
Parser status: ready

Với host=localhost và invalid line, đầu ra sẽ là:

parsing configuration: parse error at line 2: missing key-value separator
Fix line 2: missing key-value separator
Parser status: not parsed

Tự làm thử

package main

import (
	"bufio"
	"errors"
	"fmt"
	"os"
)

func main() {
	reader := bufio.NewReader(os.Stdin)
	
	// Đọc dòng đầu tiên của cấu hình
	line1, _ := reader.ReadString('\n')
	line1 = line1[:len(line1)-1] // Xóa ký tự xuống dòng ở cuối
	
	// Đọc dòng thứ hai của cấu hình
	line2, _ := reader.ReadString('\n')
	if len(line2) > 0 && line2[len(line2)-1] == '\n' {
		line2 = line2[:len(line2)-1]
	}
	
	// Nối hai dòng bằng ký tự xuống dòng để tạo nội dung cấu hình
	content := line1 + "\n" + line2
	
	// Tạo bộ phân tích cú pháp mới
	parser := NewConfigParser()
	
	// TODO: Dùng defer để luôn in ra trạng thái của bộ phân tích cú pháp ở cuối
	// Định dạng: "Parser status: [status]"
	
	// TODO: Thử phân tích nội dung
	
	// TODO: Xử lý lỗi bằng errors.Is() và errors.As()
	// - Nếu errors.Is() tìm thấy ErrEmptyContent: in ra "Error: No configuration provided"
	// - Nếu errors.Is() tìm thấy ErrInvalidFormat: in ra "Error: Configuration must have multiple lines"
	// - Nếu errors.As() tìm thấy một *ParseError: in ra toàn bộ lỗi đã được bọc,
	//   sau đó, in "Fix line [Line]: [Message]" trên dòng mới
	// - Nếu phân tích cú pháp thành công: in ra "Configuration parsed successfully"
	
	_ = errors.Is // gợi ý: dùng errors.Is cho các lỗi sentinel
	_ = errors.As // gợi ý: dùng errors.As cho các kiểu lỗi tùy chỉnh
	_ = fmt.Println
}

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch Go online