This commit is contained in:
Sky Johnson 2025-03-02 20:36:52 -06:00
parent 88c917210d
commit e7584879a3

View File

@ -7,6 +7,15 @@ import (
"io" "io"
) )
// Pre-declared errors to reduce allocations
var (
ErrUnterminatedString = errors.New("unterminated string")
ErrUnterminatedEscape = errors.New("unterminated escape sequence")
ErrUnterminatedComment = errors.New("unclosed block comment")
ErrInvalidComment = errors.New("invalid comment")
ErrNameStartWithLetter = errors.New("name must start with letter")
)
// Scanner handles the low-level parsing of the configuration format // Scanner handles the low-level parsing of the configuration format
type Scanner struct { type Scanner struct {
reader *bufio.Reader reader *bufio.Reader
@ -158,7 +167,7 @@ func (s *Scanner) NextToken() (Token, error) {
// Just a single dash // Just a single dash
_, _ = s.ReadByte() // consume dash _, _ = s.ReadByte() // consume dash
return Token{Type: TokenError, Value: []byte("unexpected '-'")}, return Token{Type: TokenError, Value: []byte("unexpected '-'")},
fmt.Errorf("unexpected '-' at line %d, column %d", startLine, startColumn) s.Error("unexpected '-'")
case b == '"': case b == '"':
return s.scanString(startLine, startColumn) return s.scanString(startLine, startColumn)
@ -171,8 +180,8 @@ func (s *Scanner) NextToken() (Token, error) {
default: default:
_, _ = s.ReadByte() // consume the unexpected character _, _ = s.ReadByte() // consume the unexpected character
err := fmt.Errorf("unexpected character: %c", b) return Token{Type: TokenError, Value: []byte(fmt.Sprintf("unexpected character: %c", b)), Line: startLine, Column: startColumn},
return Token{Type: TokenError, Value: []byte(err.Error()), Line: startLine, Column: startColumn}, err s.Error(fmt.Sprintf("unexpected character: %c", b))
} }
} }
@ -190,7 +199,7 @@ func (s *Scanner) scanComment() error {
return err return err
} }
if b != '-' { if b != '-' {
return s.Error("invalid comment") return ErrInvalidComment
} }
// Check for block comment [[ // Check for block comment [[
@ -202,7 +211,7 @@ func (s *Scanner) scanComment() error {
for { for {
b, err := s.ReadByte() b, err := s.ReadByte()
if err != nil { if err != nil {
return s.Error("unclosed block comment") return ErrUnterminatedComment
} }
if b == ']' { if b == ']' {
if n, err := s.PeekByte(); err == nil && n == ']' { if n, err := s.PeekByte(); err == nil && n == ']' {
@ -243,7 +252,7 @@ func (s *Scanner) scanString(startLine, startColumn int) (Token, error) {
for { for {
b, err := s.ReadByte() b, err := s.ReadByte()
if err != nil { if err != nil {
return Token{Type: TokenError, Value: []byte("unterminated string")}, errors.New("unterminated string") return Token{Type: TokenError, Value: []byte(ErrUnterminatedString.Error())}, ErrUnterminatedString
} }
if b == '"' { if b == '"' {
@ -254,7 +263,7 @@ func (s *Scanner) scanString(startLine, startColumn int) (Token, error) {
if b == '\\' { if b == '\\' {
escaped, err := s.ReadByte() escaped, err := s.ReadByte()
if err != nil { if err != nil {
return Token{Type: TokenError, Value: []byte("unterminated escape sequence")}, errors.New("unterminated escape sequence") return Token{Type: TokenError, Value: []byte(ErrUnterminatedEscape.Error())}, ErrUnterminatedEscape
} }
switch escaped { switch escaped {
case '"': case '"':
@ -273,7 +282,7 @@ func (s *Scanner) scanString(startLine, startColumn int) (Token, error) {
} }
} }
// Return token using buffer directly - we'll copy in NextToken if needed // Use the buffer directly - consumer is responsible for copying if needed
return Token{ return Token{
Type: TokenString, Type: TokenString,
Value: s.buffer, Value: s.buffer,
@ -294,7 +303,7 @@ func (s *Scanner) scanName(startLine, startColumn int) (Token, error) {
} }
if !isLetter(b) { if !isLetter(b) {
return Token{Type: TokenError, Value: []byte("name must start with letter")}, s.Error("name must start with letter") return Token{Type: TokenError, Value: []byte(ErrNameStartWithLetter.Error())}, ErrNameStartWithLetter
} }
s.buffer = append(s.buffer, b) s.buffer = append(s.buffer, b)
@ -314,10 +323,9 @@ func (s *Scanner) scanName(startLine, startColumn int) (Token, error) {
_, _ = s.ReadByte() _, _ = s.ReadByte()
} }
// Check if it's a boolean // Check if it's a boolean - fixed comparison
tokenType := TokenName tokenType := TokenName
if len(s.buffer) == 4 && (s.buffer[0] == 't' && s.buffer[1] == 'r' && s.buffer[2] == 'u' && s.buffer[3] == 'e' || if string(s.buffer) == "true" || string(s.buffer) == "false" {
s.buffer[0] == 'f' && s.buffer[1] == 'a' && s.buffer[2] == 'l' && s.buffer[3] == 's' && s.buffer[4] == 'e') {
tokenType = TokenBoolean tokenType = TokenBoolean
} }