maddy

Fork https://github.com/foxcpp/maddy

git clone git://git.lin.moe/go/maddy.git

  1/*
  2Maddy Mail Server - Composable all-in-one email server.
  3Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors
  4
  5This program is free software: you can redistribute it and/or modify
  6it under the terms of the GNU General Public License as published by
  7the Free Software Foundation, either version 3 of the License, or
  8(at your option) any later version.
  9
 10This program is distributed in the hope that it will be useful,
 11but WITHOUT ANY WARRANTY; without even the implied warranty of
 12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13GNU General Public License for more details.
 14
 15You should have received a copy of the GNU General Public License
 16along with this program.  If not, see <https://www.gnu.org/licenses/>.
 17*/
 18
 19// Copyright 2015 Light Code Labs, LLC
 20//
 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 at
 24//
 25//     http://www.apache.org/licenses/LICENSE-2.0
 26//
 27// Unless required by applicable law or agreed to in writing, software
 28// 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 and
 31// limitations under the License.
 32
 33package lexer
 34
 35import (
 36	"bufio"
 37	"io"
 38	"unicode"
 39)
 40
 41type (
 42	// lexer is a utility which can get values, token by
 43	// token, from a Reader. A token is a word, and tokens
 44	// are separated by whitespace. A word can be enclosed
 45	// in quotes if it contains whitespace.
 46	lexer struct {
 47		reader *bufio.Reader
 48		token  Token
 49		line   int
 50
 51		lastErr error
 52	}
 53
 54	// Token represents a single parsable unit.
 55	Token struct {
 56		File string
 57		Line int
 58		Text string
 59	}
 60)
 61
 62// 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 = 1
 67
 68	// discard byte order mark, if present
 69	firstCh, _, err := l.reader.ReadRune()
 70	if err != nil {
 71		return err
 72	}
 73	if firstCh != 0xFEFF {
 74		err := l.reader.UnreadRune()
 75		if err != nil {
 76			return err
 77		}
 78	}
 79
 80	return nil
 81}
 82
 83func (l *lexer) err() error {
 84	return l.lastErr
 85}
 86
 87// next loads the next token into the lexer.
 88//
 89// A token is delimited by whitespace, unless the token starts with a quotes
 90// character (") in which case the token goes until the closing quotes (the
 91// enclosing quotes are not included). Inside quoted strings, quotes may be
 92// escaped with a preceding \ character. No other chars may be escaped. Curly
 93// 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 from
 98// underlying Reader fails, next() returns false and err() will return the
 99// error occurred.
100func (l *lexer) next() bool {
101	var val []rune
102	var comment, quoted, escaped bool
103
104	makeToken := func() bool {
105		l.token.Text = string(val)
106		l.lastErr = nil
107		return true
108	}
109
110	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 false
118			}
119			l.lastErr = err
120			return false
121		}
122
123		if quoted {
124			if !escaped {
125				if ch == '\\' {
126					escaped = true
127					continue
128				} else if ch == '"' {
129					return makeToken()
130				}
131			}
132			if ch == '\n' {
133				l.line++
134			}
135			if escaped {
136				// only escape quotes
137				if ch != '"' {
138					val = append(val, '\\')
139				}
140			}
141			val = append(val, ch)
142			escaped = false
143			continue
144		}
145
146		if unicode.IsSpace(ch) {
147			if ch == '\r' {
148				continue
149			}
150			if ch == '\n' {
151				l.line++
152				comment = false
153			}
154			if len(val) > 0 {
155				return makeToken()
156			}
157			continue
158		}
159
160		if ch == '#' {
161			comment = true
162		}
163
164		if comment {
165			continue
166		}
167
168		if len(val) == 0 {
169			l.token = Token{Line: l.line}
170			if ch == '"' {
171				quoted = true
172				continue
173			}
174		}
175
176		val = append(val, ch)
177	}
178}