package parser import ( "bufio" "bytes" "github.com/1Panel-dev/1Panel/backend/utils/nginx/parser/flag" "io" ) type lexer struct { reader *bufio.Reader file string line int column int Latest flag.Flag } func lex(content string) *lexer { return newLexer(bytes.NewBuffer([]byte(content))) } func newLexer(r io.Reader) *lexer { return &lexer{ line: 1, reader: bufio.NewReader(r), } } func (s *lexer) scan() flag.Flag { s.Latest = s.getNextFlag() return s.Latest } //func (s *lexer) all() flag.Flags { // tokens := make([]flag.Flag, 0) // for { // v := s.scan() // if v.Type == flag.EOF || v.Type == -1 { // break // } // tokens = append(tokens, v) // } // return tokens //} func (s *lexer) getNextFlag() flag.Flag { retoFlag: ch := s.peek() switch { case isSpace(ch): s.skipWhitespace() goto retoFlag case isEOF(ch): return s.NewToken(flag.EOF).Lit(string(s.read())) case ch == ';': return s.NewToken(flag.Semicolon).Lit(string(s.read())) case ch == '{': return s.NewToken(flag.BlockStart).Lit(string(s.read())) case ch == '}': return s.NewToken(flag.BlockEnd).Lit(string(s.read())) case ch == '#': return s.scanComment() case ch == '$': return s.scanVariable() case isQuote(ch): return s.scanQuotedString(ch) default: return s.scanKeyword() } } func (s *lexer) peek() rune { r, _, _ := s.reader.ReadRune() _ = s.reader.UnreadRune() return r } type runeCheck func(rune) bool func (s *lexer) readUntil(until runeCheck) string { var buf bytes.Buffer buf.WriteRune(s.read()) for { if ch := s.peek(); isEOF(ch) { break } else if until(ch) { break } else { buf.WriteRune(s.read()) } } return buf.String() } func (s *lexer) NewToken(tokenType flag.Type) flag.Flag { return flag.Flag{ Type: tokenType, Line: s.line, Column: s.column, } } func (s *lexer) readWhile(while runeCheck) string { var buf bytes.Buffer buf.WriteRune(s.read()) for { if ch := s.peek(); while(ch) { buf.WriteRune(s.read()) } else { break } } return buf.String() } func (s *lexer) skipWhitespace() { s.readWhile(isSpace) } func (s *lexer) scanComment() flag.Flag { return s.NewToken(flag.Comment).Lit(s.readUntil(isEndOfLine)) } func (s *lexer) scanQuotedString(delimiter rune) flag.Flag { var buf bytes.Buffer tok := s.NewToken(flag.QuotedString) buf.WriteRune(s.read()) //consume delimiter for { ch := s.read() if ch == rune(flag.EOF) { panic("unexpected end of file while scanning a string, maybe an unclosed quote?") } if ch == '\\' { if needsEscape(s.peek(), delimiter) { switch s.read() { case 'n': buf.WriteRune('\n') case 'r': buf.WriteRune('\r') case 't': buf.WriteRune('\t') case '\\': buf.WriteRune('\\') case delimiter: buf.WriteRune(delimiter) } continue } } buf.WriteRune(ch) if ch == delimiter { break } } return tok.Lit(buf.String()) } func (s *lexer) scanKeyword() flag.Flag { return s.NewToken(flag.Keyword).Lit(s.readUntil(isKeywordTerminator)) } func (s *lexer) scanVariable() flag.Flag { return s.NewToken(flag.Variable).Lit(s.readUntil(isKeywordTerminator)) } func (s *lexer) read() rune { ch, _, err := s.reader.ReadRune() if err != nil { return rune(flag.EOF) } if ch == '\n' { s.column = 1 s.line++ } else { s.column++ } return ch } func isQuote(ch rune) bool { return ch == '"' || ch == '\'' || ch == '`' } func isKeywordTerminator(ch rune) bool { return isSpace(ch) || isEndOfLine(ch) || ch == '{' || ch == ';' } func needsEscape(ch, delimiter rune) bool { return ch == delimiter || ch == 'n' || ch == 't' || ch == '\\' || ch == 'r' } func isSpace(ch rune) bool { return ch == ' ' || ch == '\t' || isEndOfLine(ch) } func isEOF(ch rune) bool { return ch == rune(flag.EOF) } func isEndOfLine(ch rune) bool { return ch == '\r' || ch == '\n' }