xref: /freebsd/contrib/bc/src/lex.c (revision 10041e99a0c29c9f99c4148fc173bb12dd26aa8d)
1252884aeSStefan Eßer /*
2252884aeSStefan Eßer  * *****************************************************************************
3252884aeSStefan Eßer  *
43aa99676SStefan Eßer  * SPDX-License-Identifier: BSD-2-Clause
5252884aeSStefan Eßer  *
610328f8bSStefan Eßer  * Copyright (c) 2018-2021 Gavin D. Howard and contributors.
7252884aeSStefan Eßer  *
8252884aeSStefan Eßer  * Redistribution and use in source and binary forms, with or without
9252884aeSStefan Eßer  * modification, are permitted provided that the following conditions are met:
10252884aeSStefan Eßer  *
11252884aeSStefan Eßer  * * Redistributions of source code must retain the above copyright notice, this
12252884aeSStefan Eßer  *   list of conditions and the following disclaimer.
13252884aeSStefan Eßer  *
14252884aeSStefan Eßer  * * Redistributions in binary form must reproduce the above copyright notice,
15252884aeSStefan Eßer  *   this list of conditions and the following disclaimer in the documentation
16252884aeSStefan Eßer  *   and/or other materials provided with the distribution.
17252884aeSStefan Eßer  *
18252884aeSStefan Eßer  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19252884aeSStefan Eßer  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20252884aeSStefan Eßer  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21252884aeSStefan Eßer  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22252884aeSStefan Eßer  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23252884aeSStefan Eßer  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24252884aeSStefan Eßer  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25252884aeSStefan Eßer  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26252884aeSStefan Eßer  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27252884aeSStefan Eßer  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28252884aeSStefan Eßer  * POSSIBILITY OF SUCH DAMAGE.
29252884aeSStefan Eßer  *
30252884aeSStefan Eßer  * *****************************************************************************
31252884aeSStefan Eßer  *
32252884aeSStefan Eßer  * Common code for the lexers.
33252884aeSStefan Eßer  *
34252884aeSStefan Eßer  */
35252884aeSStefan Eßer 
36252884aeSStefan Eßer #include <assert.h>
37252884aeSStefan Eßer #include <ctype.h>
38252884aeSStefan Eßer #include <stdbool.h>
39252884aeSStefan Eßer #include <string.h>
40252884aeSStefan Eßer 
41252884aeSStefan Eßer #include <lex.h>
42252884aeSStefan Eßer #include <vm.h>
43252884aeSStefan Eßer #include <bc.h>
44252884aeSStefan Eßer 
45252884aeSStefan Eßer void bc_lex_invalidChar(BcLex *l, char c) {
46252884aeSStefan Eßer 	l->t = BC_LEX_INVALID;
4750696a6eSStefan Eßer 	bc_lex_verr(l, BC_ERR_PARSE_CHAR, c);
48252884aeSStefan Eßer }
49252884aeSStefan Eßer 
50252884aeSStefan Eßer void bc_lex_lineComment(BcLex *l) {
51252884aeSStefan Eßer 	l->t = BC_LEX_WHITESPACE;
52252884aeSStefan Eßer 	while (l->i < l->len && l->buf[l->i] != '\n') l->i += 1;
53252884aeSStefan Eßer }
54252884aeSStefan Eßer 
55252884aeSStefan Eßer void bc_lex_comment(BcLex *l) {
56252884aeSStefan Eßer 
57252884aeSStefan Eßer 	size_t i, nlines = 0;
5844d4804dSStefan Eßer 	const char *buf;
5944d4804dSStefan Eßer 	bool end = false, got_more;
60252884aeSStefan Eßer 	char c;
61252884aeSStefan Eßer 
62252884aeSStefan Eßer 	l->i += 1;
63252884aeSStefan Eßer 	l->t = BC_LEX_WHITESPACE;
64252884aeSStefan Eßer 
6544d4804dSStefan Eßer 	// This loop is complex because it might need to request more data from
6644d4804dSStefan Eßer 	// stdin if the comment is not ended. This loop is taken until the comment
6744d4804dSStefan Eßer 	// is finished or we have EOF.
6844d4804dSStefan Eßer 	do {
6944d4804dSStefan Eßer 
7044d4804dSStefan Eßer 		buf = l->buf;
7144d4804dSStefan Eßer 		got_more = false;
7244d4804dSStefan Eßer 
7344d4804dSStefan Eßer 		// If we are in stdin mode, the buffer must be the one used for stdin.
7444d4804dSStefan Eßer 		assert(!vm.is_stdin || buf == vm.buffer.v);
7544d4804dSStefan Eßer 
7644d4804dSStefan Eßer 		// Find the end of the comment.
77252884aeSStefan Eßer 		for (i = l->i; !end; i += !end) {
78252884aeSStefan Eßer 
7944d4804dSStefan Eßer 			// While we don't have an asterisk, eat, but increment nlines.
80252884aeSStefan Eßer 			for (; (c = buf[i]) && c != '*'; ++i) nlines += (c == '\n');
81252884aeSStefan Eßer 
8244d4804dSStefan Eßer 			// If this is true, we need to request more data.
83252884aeSStefan Eßer 			if (BC_ERR(!c || buf[i + 1] == '\0')) {
8444d4804dSStefan Eßer 
8544d4804dSStefan Eßer 				// Read more.
8644d4804dSStefan Eßer 				if (!vm.eof && l->is_stdin) got_more = bc_lex_readLine(l);
8744d4804dSStefan Eßer 
8844d4804dSStefan Eßer 				break;
89252884aeSStefan Eßer 			}
90252884aeSStefan Eßer 
9144d4804dSStefan Eßer 			// If this turns true, we found the end. Yay!
9244d4804dSStefan Eßer 			end = (buf[i + 1] == '/');
9344d4804dSStefan Eßer 		}
9444d4804dSStefan Eßer 
9544d4804dSStefan Eßer 	} while (got_more && !end);
9644d4804dSStefan Eßer 
9744d4804dSStefan Eßer 	// If we didn't find the end, barf.
9844d4804dSStefan Eßer 	if (!end) {
9944d4804dSStefan Eßer 		l->i = i;
10044d4804dSStefan Eßer 		bc_lex_err(l, BC_ERR_PARSE_COMMENT);
101252884aeSStefan Eßer 	}
102252884aeSStefan Eßer 
103252884aeSStefan Eßer 	l->i = i + 2;
104252884aeSStefan Eßer 	l->line += nlines;
105252884aeSStefan Eßer }
106252884aeSStefan Eßer 
107252884aeSStefan Eßer void bc_lex_whitespace(BcLex *l) {
10844d4804dSStefan Eßer 
109252884aeSStefan Eßer 	char c;
11044d4804dSStefan Eßer 
111252884aeSStefan Eßer 	l->t = BC_LEX_WHITESPACE;
11244d4804dSStefan Eßer 
11344d4804dSStefan Eßer 	// Eat. We don't eat newlines because they can be special.
114252884aeSStefan Eßer 	for (c = l->buf[l->i]; c != '\n' && isspace(c); c = l->buf[++l->i]);
115252884aeSStefan Eßer }
116252884aeSStefan Eßer 
117252884aeSStefan Eßer void bc_lex_commonTokens(BcLex *l, char c) {
118252884aeSStefan Eßer 	if (!c) l->t = BC_LEX_EOF;
119252884aeSStefan Eßer 	else if (c == '\n') l->t = BC_LEX_NLINE;
120252884aeSStefan Eßer 	else bc_lex_whitespace(l);
121252884aeSStefan Eßer }
122252884aeSStefan Eßer 
12344d4804dSStefan Eßer /**
12444d4804dSStefan Eßer  * Parses a number.
12544d4804dSStefan Eßer  * @param l         The lexer.
12644d4804dSStefan Eßer  * @param start     The start character.
12744d4804dSStefan Eßer  * @param int_only  Whether this function should only look for an integer. This
12844d4804dSStefan Eßer  *                  is used to implement the exponent of scientific notation.
12944d4804dSStefan Eßer  */
130252884aeSStefan Eßer static size_t bc_lex_num(BcLex *l, char start, bool int_only) {
131252884aeSStefan Eßer 
132252884aeSStefan Eßer 	const char *buf = l->buf + l->i;
133252884aeSStefan Eßer 	size_t i;
134252884aeSStefan Eßer 	char c;
135252884aeSStefan Eßer 	bool last_pt, pt = (start == '.');
136252884aeSStefan Eßer 
13744d4804dSStefan Eßer 	// This loop looks complex. It is not. It is asking if the character is not
13844d4804dSStefan Eßer 	// a nul byte and it if it a valid num character based on what we have found
13944d4804dSStefan Eßer 	// thus far, or whether it is a backslash followed by a newline. I can do
14044d4804dSStefan Eßer 	// i+1 on the buffer because the buffer must have a nul byte.
141252884aeSStefan Eßer 	for (i = 0; (c = buf[i]) && (BC_LEX_NUM_CHAR(c, pt, int_only) ||
142252884aeSStefan Eßer 	                             (c == '\\' && buf[i + 1] == '\n')); ++i)
143252884aeSStefan Eßer 	{
14444d4804dSStefan Eßer 		// I don't need to test that the next character is a newline because
14544d4804dSStefan Eßer 		// the loop condition above ensures that.
146252884aeSStefan Eßer 		if (c == '\\') {
147252884aeSStefan Eßer 
148252884aeSStefan Eßer 			i += 2;
149252884aeSStefan Eßer 
150252884aeSStefan Eßer 			// Make sure to eat whitespace at the beginning of the line.
151252884aeSStefan Eßer 			while(isspace(buf[i]) && buf[i] != '\n') i += 1;
152252884aeSStefan Eßer 
153252884aeSStefan Eßer 			c = buf[i];
154252884aeSStefan Eßer 
15544d4804dSStefan Eßer 			// If the next character is not a number character, bail.
156252884aeSStefan Eßer 			if (!BC_LEX_NUM_CHAR(c, pt, int_only)) break;
157252884aeSStefan Eßer 		}
158252884aeSStefan Eßer 
15944d4804dSStefan Eßer 		// Did we find the radix point?
160252884aeSStefan Eßer 		last_pt = (c == '.');
16144d4804dSStefan Eßer 
16244d4804dSStefan Eßer 		// If we did, and we already have one, then break because it's not part
16344d4804dSStefan Eßer 		// of this number.
164252884aeSStefan Eßer 		if (pt && last_pt) break;
16544d4804dSStefan Eßer 
16644d4804dSStefan Eßer 		// Set whether we have found a radix point.
167252884aeSStefan Eßer 		pt = pt || last_pt;
168252884aeSStefan Eßer 
169252884aeSStefan Eßer 		bc_vec_push(&l->str, &c);
170252884aeSStefan Eßer 	}
171252884aeSStefan Eßer 
172252884aeSStefan Eßer 	return i;
173252884aeSStefan Eßer }
174252884aeSStefan Eßer 
175252884aeSStefan Eßer void bc_lex_number(BcLex *l, char start) {
176252884aeSStefan Eßer 
177252884aeSStefan Eßer 	l->t = BC_LEX_NUMBER;
178252884aeSStefan Eßer 
17944d4804dSStefan Eßer 	// Make sure the string is clear.
18010328f8bSStefan Eßer 	bc_vec_popAll(&l->str);
181252884aeSStefan Eßer 	bc_vec_push(&l->str, &start);
182252884aeSStefan Eßer 
18344d4804dSStefan Eßer 	// Parse the number.
184252884aeSStefan Eßer 	l->i += bc_lex_num(l, start, false);
185252884aeSStefan Eßer 
186252884aeSStefan Eßer #if BC_ENABLE_EXTRA_MATH
187252884aeSStefan Eßer 	{
188252884aeSStefan Eßer 		char c = l->buf[l->i];
189252884aeSStefan Eßer 
19044d4804dSStefan Eßer 		// Do we have a number in scientific notation?
191252884aeSStefan Eßer 		if (c == 'e') {
192252884aeSStefan Eßer 
193252884aeSStefan Eßer #if BC_ENABLED
19444d4804dSStefan Eßer 			// Barf for POSIX.
19550696a6eSStefan Eßer 			if (BC_IS_POSIX) bc_lex_err(l, BC_ERR_POSIX_EXP_NUM);
196252884aeSStefan Eßer #endif // BC_ENABLED
197252884aeSStefan Eßer 
19844d4804dSStefan Eßer 			// Push the e.
199252884aeSStefan Eßer 			bc_vec_push(&l->str, &c);
200252884aeSStefan Eßer 			l->i += 1;
201252884aeSStefan Eßer 			c = l->buf[l->i];
202252884aeSStefan Eßer 
20344d4804dSStefan Eßer 			// Check for negative specifically because bc_lex_num() does not.
204252884aeSStefan Eßer 			if (c == BC_LEX_NEG_CHAR) {
205252884aeSStefan Eßer 				bc_vec_push(&l->str, &c);
206252884aeSStefan Eßer 				l->i += 1;
207252884aeSStefan Eßer 				c = l->buf[l->i];
208252884aeSStefan Eßer 			}
209252884aeSStefan Eßer 
21044d4804dSStefan Eßer 			// We must have a number character, so barf if not.
211252884aeSStefan Eßer 			if (BC_ERR(!BC_LEX_NUM_CHAR(c, false, true)))
21250696a6eSStefan Eßer 				bc_lex_verr(l, BC_ERR_PARSE_CHAR, c);
213252884aeSStefan Eßer 
21444d4804dSStefan Eßer 			// Parse the exponent.
215252884aeSStefan Eßer 			l->i += bc_lex_num(l, 0, true);
216252884aeSStefan Eßer 		}
217252884aeSStefan Eßer 	}
218252884aeSStefan Eßer #endif // BC_ENABLE_EXTRA_MATH
219252884aeSStefan Eßer 
220252884aeSStefan Eßer 	bc_vec_pushByte(&l->str, '\0');
221252884aeSStefan Eßer }
222252884aeSStefan Eßer 
223252884aeSStefan Eßer void bc_lex_name(BcLex *l) {
224252884aeSStefan Eßer 
225252884aeSStefan Eßer 	size_t i = 0;
226252884aeSStefan Eßer 	const char *buf = l->buf + l->i - 1;
227252884aeSStefan Eßer 	char c = buf[i];
228252884aeSStefan Eßer 
229252884aeSStefan Eßer 	l->t = BC_LEX_NAME;
230252884aeSStefan Eßer 
23144d4804dSStefan Eßer 	// Should be obvious. It's looking for valid characters.
232252884aeSStefan Eßer 	while ((c >= 'a' && c <= 'z') || isdigit(c) || c == '_') c = buf[++i];
233252884aeSStefan Eßer 
23444d4804dSStefan Eßer 	// Set the string to the identifier.
235252884aeSStefan Eßer 	bc_vec_string(&l->str, i, buf);
236252884aeSStefan Eßer 
237252884aeSStefan Eßer 	// Increment the index. We minus 1 because it has already been incremented.
238252884aeSStefan Eßer 	l->i += i - 1;
239252884aeSStefan Eßer }
240252884aeSStefan Eßer 
241252884aeSStefan Eßer void bc_lex_init(BcLex *l) {
242252884aeSStefan Eßer 	BC_SIG_ASSERT_LOCKED;
243252884aeSStefan Eßer 	assert(l != NULL);
24444d4804dSStefan Eßer 	bc_vec_init(&l->str, sizeof(char), BC_DTOR_NONE);
245252884aeSStefan Eßer }
246252884aeSStefan Eßer 
247252884aeSStefan Eßer void bc_lex_free(BcLex *l) {
248252884aeSStefan Eßer 	BC_SIG_ASSERT_LOCKED;
249252884aeSStefan Eßer 	assert(l != NULL);
250252884aeSStefan Eßer 	bc_vec_free(&l->str);
251252884aeSStefan Eßer }
252252884aeSStefan Eßer 
253252884aeSStefan Eßer void bc_lex_file(BcLex *l, const char *file) {
254252884aeSStefan Eßer 	assert(l != NULL && file != NULL);
255252884aeSStefan Eßer 	l->line = 1;
256252884aeSStefan Eßer 	vm.file = file;
257252884aeSStefan Eßer }
258252884aeSStefan Eßer 
259252884aeSStefan Eßer void bc_lex_next(BcLex *l) {
260252884aeSStefan Eßer 
261*10041e99SStefan Eßer 	BC_SIG_ASSERT_LOCKED;
262*10041e99SStefan Eßer 
263252884aeSStefan Eßer 	assert(l != NULL);
264252884aeSStefan Eßer 
265252884aeSStefan Eßer 	l->last = l->t;
26644d4804dSStefan Eßer 
26744d4804dSStefan Eßer 	// If this wasn't here, the line number would be off.
268252884aeSStefan Eßer 	l->line += (l->i != 0 && l->buf[l->i - 1] == '\n');
269252884aeSStefan Eßer 
27044d4804dSStefan Eßer 	// If the last token was EOF, someone called this one too many times.
27150696a6eSStefan Eßer 	if (BC_ERR(l->last == BC_LEX_EOF)) bc_lex_err(l, BC_ERR_PARSE_EOF);
272252884aeSStefan Eßer 
273252884aeSStefan Eßer 	l->t = BC_LEX_EOF;
274252884aeSStefan Eßer 
27544d4804dSStefan Eßer 	// We are done if this is true.
276252884aeSStefan Eßer 	if (l->i == l->len) return;
277252884aeSStefan Eßer 
278252884aeSStefan Eßer 	// Loop until failure or we don't have whitespace. This
279252884aeSStefan Eßer 	// is so the parser doesn't get inundated with whitespace.
280252884aeSStefan Eßer 	do {
281252884aeSStefan Eßer 		vm.next(l);
282252884aeSStefan Eßer 	} while (l->t == BC_LEX_WHITESPACE);
283252884aeSStefan Eßer }
284252884aeSStefan Eßer 
28544d4804dSStefan Eßer /**
28644d4804dSStefan Eßer  * Updates the buffer and len so that they are not invalidated when the stdin
28744d4804dSStefan Eßer  * buffer grows.
28844d4804dSStefan Eßer  * @param l     The lexer.
28944d4804dSStefan Eßer  * @param text  The text.
29044d4804dSStefan Eßer  * @param len   The length of the text.
29144d4804dSStefan Eßer  */
29244d4804dSStefan Eßer static void bc_lex_fixText(BcLex *l, const char *text, size_t len) {
293252884aeSStefan Eßer 	l->buf = text;
29444d4804dSStefan Eßer 	l->len = len;
29544d4804dSStefan Eßer }
29644d4804dSStefan Eßer 
29744d4804dSStefan Eßer bool bc_lex_readLine(BcLex *l) {
29844d4804dSStefan Eßer 
299*10041e99SStefan Eßer 	bool good;
300*10041e99SStefan Eßer 
301*10041e99SStefan Eßer 	// These are reversed because they should be already locked, but
302*10041e99SStefan Eßer 	// bc_vm_readLine() needs them to be unlocked.
303*10041e99SStefan Eßer 	BC_SIG_UNLOCK;
304*10041e99SStefan Eßer 
305*10041e99SStefan Eßer 	good = bc_vm_readLine(false);
306*10041e99SStefan Eßer 
307*10041e99SStefan Eßer 	BC_SIG_LOCK;
30844d4804dSStefan Eßer 
30944d4804dSStefan Eßer 	bc_lex_fixText(l, vm.buffer.v, vm.buffer.len - 1);
31044d4804dSStefan Eßer 
31144d4804dSStefan Eßer 	return good;
31244d4804dSStefan Eßer }
31344d4804dSStefan Eßer 
31444d4804dSStefan Eßer void bc_lex_text(BcLex *l, const char *text, bool is_stdin) {
315*10041e99SStefan Eßer 
316*10041e99SStefan Eßer 	BC_SIG_ASSERT_LOCKED;
317*10041e99SStefan Eßer 
31844d4804dSStefan Eßer 	assert(l != NULL && text != NULL);
319*10041e99SStefan Eßer 
32044d4804dSStefan Eßer 	bc_lex_fixText(l, text, strlen(text));
321252884aeSStefan Eßer 	l->i = 0;
322252884aeSStefan Eßer 	l->t = l->last = BC_LEX_INVALID;
32344d4804dSStefan Eßer 	l->is_stdin = is_stdin;
324*10041e99SStefan Eßer 
325252884aeSStefan Eßer 	bc_lex_next(l);
326252884aeSStefan Eßer }
327