1/*2Maddy Mail Server - Composable all-in-one email server.3Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors45This program is free software: you can redistribute it and/or modify6it under the terms of the GNU General Public License as published by7the Free Software Foundation, either version 3 of the License, or8(at your option) any later version.910This program is distributed in the hope that it will be useful,11but WITHOUT ANY WARRANTY; without even the implied warranty of12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the13GNU General Public License for more details.1415You should have received a copy of the GNU General Public License16along with this program. If not, see <https://www.gnu.org/licenses/>.17*/1819// Copyright 2015 Light Code Labs, LLC20//21// Licensed under the Apache License, Version 2.0 (the "License");22// you may not use this file except in compliance with the License.23// You may obtain a copy of the License at24//25// http://www.apache.org/licenses/LICENSE-2.026//27// Unless required by applicable law or agreed to in writing, software28// distributed under the License is distributed on an "AS IS" BASIS,29// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.30// See the License for the specific language governing permissions and31// limitations under the License.3233package lexer3435import (36 "bufio"37 "io"38 "unicode"39)4041type (42 // lexer is a utility which can get values, token by43 // token, from a Reader. A token is a word, and tokens44 // are separated by whitespace. A word can be enclosed45 // in quotes if it contains whitespace.46 lexer struct {47 reader *bufio.Reader48 token Token49 line int5051 lastErr error52 }5354 // Token represents a single parsable unit.55 Token struct {56 File string57 Line int58 Text string59 }60)6162// load prepares the lexer to scan an input for tokens.63// It discards any leading byte order mark.64func (l *lexer) load(input io.Reader) error {65 l.reader = bufio.NewReader(input)66 l.line = 16768 // discard byte order mark, if present69 firstCh, _, err := l.reader.ReadRune()70 if err != nil {71 return err72 }73 if firstCh != 0xFEFF {74 err := l.reader.UnreadRune()75 if err != nil {76 return err77 }78 }7980 return nil81}8283func (l *lexer) err() error {84 return l.lastErr85}8687// next loads the next token into the lexer.88//89// A token is delimited by whitespace, unless the token starts with a quotes90// character (") in which case the token goes until the closing quotes (the91// enclosing quotes are not included). Inside quoted strings, quotes may be92// escaped with a preceding \ character. No other chars may be escaped. Curly93// braces ('{', '}') are emitted as a separate tokens.94//95// The rest of the line is skipped if a "#" character is read in.96//97// Returns true if a token was loaded; false otherwise. If read from98// underlying Reader fails, next() returns false and err() will return the99// error occurred.100func (l *lexer) next() bool {101 var val []rune102 var comment, quoted, escaped bool103104 makeToken := func() bool {105 l.token.Text = string(val)106 l.lastErr = nil107 return true108 }109110 for {111 ch, _, err := l.reader.ReadRune()112 if err != nil {113 if len(val) > 0 {114 return makeToken()115 }116 if err == io.EOF {117 return false118 }119 l.lastErr = err120 return false121 }122123 if quoted {124 if !escaped {125 if ch == '\\' {126 escaped = true127 continue128 } else if ch == '"' {129 return makeToken()130 }131 }132 if ch == '\n' {133 l.line++134 }135 if escaped {136 // only escape quotes137 if ch != '"' {138 val = append(val, '\\')139 }140 }141 val = append(val, ch)142 escaped = false143 continue144 }145146 if unicode.IsSpace(ch) {147 if ch == '\r' {148 continue149 }150 if ch == '\n' {151 l.line++152 comment = false153 }154 if len(val) > 0 {155 return makeToken()156 }157 continue158 }159160 if ch == '#' {161 comment = true162 }163164 if comment {165 continue166 }167168 if len(val) == 0 {169 l.token = Token{Line: l.line}170 if ch == '"' {171 quoted = true172 continue173 }174 }175176 val = append(val, ch)177 }178}