package parser import ( "bufio" "fmt" "strings" ) const ( startState = iota attrNameState attrValueState attrColonState multilineState ) type baseState struct { r *bufio.Reader lineNumber *int } func newBaseState(r *bufio.Reader, lineNumber *int) *baseState { return &baseState{ r: r, lineNumber: lineNumber, } } func (s *baseState) parseProperties() (map[string]string, error) { var r = s.r var attributes = make(map[string]string) var builder strings.Builder var key string var st = startState for { ch, err := s.readSkipComment() if err != nil { return nil, err } switch st { case startState: switch { case isNewline(ch): return nil, fmt.Errorf("%q should be on the same line with %q", leftParenthesis, infoDirective) case isSpace(ch): continue case ch == leftParenthesis: st = attrNameState default: return nil, fmt.Errorf("unexpected char %q after %q", ch, infoDirective) } case attrNameState: switch { case isNewline(ch): if builder.Len() > 0 { return nil, fmt.Errorf("unexpected newline after %q", builder.String()) } case isLetterDigit(ch): builder.WriteRune(ch) case isSpace(ch): if builder.Len() > 0 { key = builder.String() builder.Reset() st = attrColonState } case ch == colon: if builder.Len() == 0 { return nil, fmt.Errorf("unexpected leading %q", ch) } key = builder.String() builder.Reset() st = attrValueState case ch == rightParenthesis: return attributes, nil } case attrColonState: switch { case isSpace(ch): continue case ch == colon: st = attrValueState default: return nil, fmt.Errorf("bad char %q after %q in %q", ch, key, infoDirective) } case attrValueState: switch { case ch == multilineBeginTag: if builder.Len() > 0 { return nil, fmt.Errorf("%q before %q", builder.String(), multilineBeginTag) } else { st = multilineState } case isSpace(ch): if builder.Len() > 0 { builder.WriteRune(ch) } case isNewline(ch): attributes[key] = builder.String() builder.Reset() st = attrNameState case ch == rightParenthesis: attributes[key] = builder.String() builder.Reset() return attributes, nil default: builder.WriteRune(ch) } case multilineState: switch { case ch == multilineEndTag: attributes[key] = builder.String() builder.Reset() st = attrNameState case isNewline(ch): var multipleNewlines bool loopAfterNewline: for { next, err := read(r) if err != nil { return nil, err } switch { case isSpace(next): continue case isNewline(next): multipleNewlines = true default: if err := unread(r); err != nil { return nil, err } break loopAfterNewline } } if multipleNewlines { fmt.Fprintln(&builder) } else { builder.WriteByte(' ') } case ch == rightParenthesis: if builder.Len() > 0 { attributes[key] = builder.String() builder.Reset() } return attributes, nil default: builder.WriteRune(ch) } } } } func (s *baseState) read() (rune, error) { value, err := read(s.r) if err != nil { return 0, err } if isNewline(value) { *s.lineNumber++ } return value, nil } func (s *baseState) readSkipComment() (rune, error) { ch, err := s.read() if err != nil { return 0, err } if isSlash(ch) { value, err := s.mayReadToEndOfLine() if err != nil { return 0, err } if value > 0 { ch = value } } return ch, nil } func (s *baseState) mayReadToEndOfLine() (rune, error) { ch, err := s.read() if err != nil { return 0, err } if isSlash(ch) { for { value, err := s.read() if err != nil { return 0, err } if isNewline(value) { return value, nil } } } err = s.unread() return 0, err } func (s *baseState) readLineSkipComment() (string, error) { line, err := s.readLine() if err != nil { return "", err } var commentIdx = strings.Index(line, "//") if commentIdx >= 0 { return line[:commentIdx], nil } return line, nil } func (s *baseState) readLine() (string, error) { line, _, err := s.r.ReadLine() if err != nil { return "", err } *s.lineNumber++ return string(line), nil } func (s *baseState) skipSpaces() error { return skipSpaces(s.r) } func (s *baseState) unread() error { return unread(s.r) }