1/*
2 * Copyright © 2010 Intel Corporation
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
22 */
23
24#include <assert.h>
25#include <string.h>
26#include <ctype.h>
27#include "glcpp.h"
28
29void
30glcpp_error (YYLTYPE *locp, glcpp_parser_t *parser, const char *fmt, ...)
31{
32	va_list ap;
33
34	parser->error = 1;
35	ralloc_asprintf_rewrite_tail(&parser->info_log,
36				     &parser->info_log_length,
37				     "%u:%u(%u): "
38				     "preprocessor error: ",
39				     locp->source,
40				     locp->first_line,
41				     locp->first_column);
42	va_start(ap, fmt);
43	ralloc_vasprintf_rewrite_tail(&parser->info_log,
44				      &parser->info_log_length,
45				      fmt, ap);
46	va_end(ap);
47	ralloc_asprintf_rewrite_tail(&parser->info_log,
48				     &parser->info_log_length, "\n");
49}
50
51void
52glcpp_warning (YYLTYPE *locp, glcpp_parser_t *parser, const char *fmt, ...)
53{
54	va_list ap;
55
56	ralloc_asprintf_rewrite_tail(&parser->info_log,
57				     &parser->info_log_length,
58				     "%u:%u(%u): "
59				     "preprocessor warning: ",
60				     locp->source,
61				     locp->first_line,
62				     locp->first_column);
63	va_start(ap, fmt);
64	ralloc_vasprintf_rewrite_tail(&parser->info_log,
65				      &parser->info_log_length,
66				      fmt, ap);
67	va_end(ap);
68	ralloc_asprintf_rewrite_tail(&parser->info_log,
69				     &parser->info_log_length, "\n");
70}
71
72/* Given str, (that's expected to start with a newline terminator of some
73 * sort), return a pointer to the first character in str after the newline.
74 *
75 * A newline terminator can be any of the following sequences:
76 *
77 *	"\r\n"
78 *	"\n\r"
79 *	"\n"
80 *	"\r"
81 *
82 * And the longest such sequence will be skipped.
83 */
84static const char *
85skip_newline (const char *str)
86{
87	const char *ret = str;
88
89	if (ret == NULL)
90		return ret;
91
92	if (*ret == '\0')
93		return ret;
94
95	if (*ret == '\r') {
96		ret++;
97		if (*ret && *ret == '\n')
98			ret++;
99	} else if (*ret == '\n') {
100		ret++;
101		if (*ret && *ret == '\r')
102			ret++;
103	}
104
105	return ret;
106}
107
108/* Remove any line continuation characters in the shader, (whether in
109 * preprocessing directives or in GLSL code).
110 */
111static char *
112remove_line_continuations(glcpp_parser_t *ctx, const char *shader)
113{
114	char *clean = ralloc_strdup(ctx, "");
115	const char *backslash, *newline, *search_start;
116        const char *cr, *lf;
117        char newline_separator[3];
118	int collapsed_newlines = 0;
119
120	search_start = shader;
121
122	/* Determine what flavor of newlines this shader is using. GLSL
123	 * provides for 4 different possible ways to separate lines, (using
124	 * one or two characters):
125	 *
126	 *	"\n" (line-feed, like Linux, Unix, and new Mac OS)
127	 *	"\r" (carriage-return, like old Mac files)
128	 *	"\r\n" (carriage-return + line-feed, like DOS files)
129	 *	"\n\r" (line-feed + carriage-return, like nothing, really)
130	 *
131	 * This code explicitly supports a shader that uses a mixture of
132	 * newline terminators and will properly handle line continuation
133	 * backslashes followed by any of the above.
134	 *
135	 * But, since we must also insert additional newlines in the output
136	 * (for any collapsed lines) we attempt to maintain consistency by
137	 * examining the first encountered newline terminator, and using the
138	 * same terminator for any newlines we insert.
139	 */
140	cr = strchr(search_start, '\r');
141	lf = strchr(search_start, '\n');
142
143	newline_separator[0] = '\n';
144	newline_separator[1] = '\0';
145	newline_separator[2] = '\0';
146
147	if (cr == NULL) {
148		/* Nothing to do. */
149	} else if (lf == NULL) {
150		newline_separator[0] = '\r';
151	} else if (lf == cr + 1) {
152		newline_separator[0] = '\r';
153		newline_separator[1] = '\n';
154	} else if (cr == lf + 1) {
155		newline_separator[0] = '\n';
156		newline_separator[1] = '\r';
157	}
158
159	while (true) {
160		backslash = strchr(search_start, '\\');
161
162		/* If we have previously collapsed any line-continuations,
163		 * then we want to insert additional newlines at the next
164		 * occurrence of a newline character to avoid changing any
165		 * line numbers.
166		 */
167		if (collapsed_newlines) {
168			cr = strchr (search_start, '\r');
169			lf = strchr (search_start, '\n');
170			if (cr && lf)
171				newline = cr < lf ? cr : lf;
172			else if (cr)
173				newline = cr;
174			else
175				newline = lf;
176			if (newline &&
177			    (backslash == NULL || newline < backslash))
178			{
179				ralloc_strncat(&clean, shader,
180					       newline - shader + 1);
181				while (collapsed_newlines) {
182					ralloc_strcat(&clean, newline_separator);
183					collapsed_newlines--;
184				}
185				shader = skip_newline (newline);
186				search_start = shader;
187			}
188		}
189
190		search_start = backslash + 1;
191
192		if (backslash == NULL)
193			break;
194
195		/* At each line continuation, (backslash followed by a
196		 * newline), copy all preceding text to the output, then
197		 * advance the shader pointer to the character after the
198		 * newline.
199		 */
200		if (backslash[1] == '\r' || backslash[1] == '\n')
201		{
202			collapsed_newlines++;
203			ralloc_strncat(&clean, shader, backslash - shader);
204			shader = skip_newline (backslash + 1);
205			search_start = shader;
206		}
207	}
208
209	ralloc_strcat(&clean, shader);
210
211	return clean;
212}
213
214int
215glcpp_preprocess(void *ralloc_ctx, const char **shader, char **info_log,
216                 glcpp_extension_iterator extensions, void *state,
217                 struct gl_context *gl_ctx)
218{
219	int errors;
220	glcpp_parser_t *parser =
221		glcpp_parser_create(extensions, state, gl_ctx->API);
222
223	if (! gl_ctx->Const.DisableGLSLLineContinuations)
224		*shader = remove_line_continuations(parser, *shader);
225
226	glcpp_lex_set_source_string (parser, *shader);
227
228	glcpp_parser_parse (parser);
229
230	if (parser->skip_stack)
231		glcpp_error (&parser->skip_stack->loc, parser, "Unterminated #if\n");
232
233	glcpp_parser_resolve_implicit_version(parser);
234
235	ralloc_strcat(info_log, parser->info_log);
236
237	ralloc_steal(ralloc_ctx, parser->output);
238	*shader = parser->output;
239
240	errors = parser->error;
241	glcpp_parser_destroy (parser);
242	return errors;
243}
244