changeset 292:40ecbd5da481 ccdev

Part one of the C preprocessor This is part one of the C preprocessor. It finds and then fails to intepret directives. Also handles line splicing and trigraphs.
author William Astle <lost@l-w.ca>
date Sun, 08 Sep 2013 21:58:12 -0600
parents 83f682ed4d65
children c419b3b3d43f
files .hgignore Makefile lwcc/README.txt lwcc/cpp/cpp.h lwcc/cpp/error.c lwcc/cpp/file.c lwcc/cpp/main.c
diffstat 7 files changed, 913 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Sun Sep 08 17:08:50 2013 -0600
+++ b/.hgignore	Sun Sep 08 21:58:12 2013 -0600
@@ -10,3 +10,4 @@
 /lwar$
 /lwasm$
 /lwcc$
+/lwcc-cpp$
--- a/Makefile	Sun Sep 08 17:08:50 2013 -0600
+++ b/Makefile	Sun Sep 08 21:58:12 2013 -0600
@@ -55,9 +55,10 @@
 	lwlink/lwlink$(PROGSUFFIX) \
 	lwar/lwar$(PROGSUFFIX) \
 	lwlink/lwobjdump$(PROGSUFFIX) \
-	lwcc/driver/lwcc$(PROGSUFFIX)
+	lwcc/driver/lwcc$(PROGSUFFIX) \
+	lwcc/cpp/lwcc-cpp$(PROGSUFFIX)
 
-LWCC_LIBBIN_FILES =
+LWCC_LIBBIN_FILES = lwcc/cpp/lwcc-cpp$(PROGSUFFIX)
 LWCC_LIBLIB_FILES =
 LWCC_LIBINC_FILES =
 
@@ -100,12 +101,18 @@
 lwcc_driver_objs := $(lwcc_driver_srcs:.c=.o)
 lwcc_driver_deps := $(lwcc_driver_srcs:.c=.d)
 
+lwcc_cpp_srcs := main.c error.c file.c
+lwcc_cpp_srcs := $(addprefix lwcc/cpp/,$(lwcc_cpp_srcs))
+lwcc_cpp_objs := $(lwcc_cpp_srcs:.c=.o)
+lwcc_cpp_deps := $(lwcc_cpp_srcs:.c=.d)
+
 .PHONY: lwlink lwasm lwar lwobjdump lwcc
 lwlink: lwlink/lwlink$(PROGSUFFIX)
 lwasm: lwasm/lwasm$(PROGSUFFIX)
 lwar: lwar/lwar$(PROGSUFFIX)
 lwobjdump: lwlink/lwobjdump$(PROGSUFFIX)
-lwcc: lwcc/driver/lwcc
+lwcc: lwcc/driver/lwcc$(PROGSUFFIX)
+lwcc-cpp: lwcc/cpp/lwcc-cpp$(PROGSUFFIX)
 
 lwasm/lwasm$(PROGSUFFIX): $(lwasm_objs) lwlib
 	@echo Linking $@
@@ -127,6 +134,10 @@
 	@echo Linking $@
 	@$(CC) -o $@ $(lwcc_driver_objs) $(LDFLAGS)
 
+lwcc/cpp/lwcc-cpp$(PROGSUFFIX): $(lwcc_cpp_objs) lwlib
+	@echo Linking $@
+	@$(CC) -o $@ $(lwcc_cpp_objs) $(LDFLAGS)
+
 #.PHONY: lwlib
 .INTERMEDIATE: lwlib
 lwlib: lwlib/liblw.a
@@ -157,8 +168,8 @@
 clean: $(cleantargs)
 	@echo "Cleaning up"
 	@rm -f lwlib/liblw.a lwasm/lwasm$(PROGSUFFIX) lwlink/lwlink$(PROGSUFFIX) lwlink/lwobjdump$(PROGSUFFIX) lwar/lwar$(PROGSUFFIX)
-	@rm -f lwcc/driver/lwcc$(PROGSUFFIX)
-	@rm -f $(lwcc_driver_ojbs)
+	@rm -f lwcc/driver/lwcc$(PROGSUFFIX) lwcc/cpp/lwcc-cpp$(PROGSUFFIX)
+	@rm -f $(lwcc_driver_ojbs) $(lwcc_preproc_objs)
 	@rm -f $(lwasm_objs) $(lwlink_objs) $(lwar_objs) $(lwlib_objs) $(lwobjdump_objs)
 	@rm -f $(extra_clean)
 	@rm -f */*.exe
@@ -182,13 +193,13 @@
 	install -d $(LWCC_INSTALLLIBDIR)/lib
 	install -d $(LWCC_INSTALLLIBDIR)/include
 ifneq ($(LWCC_LIBBIN_FILES),)
-	install $(LWCC_LIBBIN_FILES) $(LIBCC_INSTALLLIBDIR)/bin
+	install $(LWCC_LIBBIN_FILES) $(LWCC_INSTALLLIBDIR)/bin
 endif
 ifneq ($(LWCC_LIBLIB_FILES),)
-	install $(LWCC_LIBLIB_FILES) $(LIBCC_INSTALLLIBDIR)/lib
+	install $(LWCC_LIBLIB_FILES) $(LWCC_INSTALLLIBDIR)/lib
 endif
 ifneq ($(LWCC_LIBINC_FILES),)
-	install $(LWCC_LIBINC_FILES) $(LIBCC_INSTALLLIBDIR)/include
+	install $(LWCC_LIBINC_FILES) $(LWCC_INSTALLLIBDIR)/include
 endif
 
 .PHONY: test
--- a/lwcc/README.txt	Sun Sep 08 17:08:50 2013 -0600
+++ b/lwcc/README.txt	Sun Sep 08 21:58:12 2013 -0600
@@ -17,6 +17,13 @@
 likely to change substantially.
 
 
+cpp/
+
+This is the actual C preprocessor. Its specific interface is deliberately
+undocumented. Do not call it directly. Ever. Just don't. Bad Things(tm) will
+happen if you do.
+
+
 liblwcc/
 
 This contains any runtime libraries the compiler needs to support its
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lwcc/cpp/cpp.h	Sun Sep 08 21:58:12 2013 -0600
@@ -0,0 +1,57 @@
+/*
+lwcc/cpp/cpp.h
+
+Copyright © 2013 William Astle
+
+This file is part of LWTOOLS.
+
+LWTOOLS is free software: you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later
+version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef cpp_h_seen___
+#define cpp_h_seen___
+
+#include <stdio.h>
+
+enum
+{
+	CPP_NOUNG = -3,
+	CPP_EOL = -2,
+	CPP_EOF = -1,
+};
+
+struct file_stack_e
+{
+	const char *fn;
+	FILE *fp;
+	struct file_stack_e *next;
+	int line;
+	int col;
+	int eolstate;			// end of line state for interpreting \r\n \n\r \n \r
+	int ra;					// read ahead byte for trigraph scan
+	int qseen;				// number of ? seen during trigraph scan
+	int unget;				// character that has been "ungot"
+	int curc;				// the most recent character retrieved
+};
+
+extern FILE *output_fp;
+extern int trigraphs;
+extern struct file_stack_e *file_stack;
+
+extern int process_file(const char *);
+
+extern void do_error(const char *, ...);
+extern void do_warning(const char *, ...);
+
+#endif // cpp_h_seen___
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lwcc/cpp/error.c	Sun Sep 08 21:58:12 2013 -0600
@@ -0,0 +1,59 @@
+/*
+lwcc/cpp/error.c
+
+Copyright © 2013 William Astle
+
+This file is part of LWTOOLS.
+
+LWTOOLS is free software: you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later
+version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "cpp.h"
+
+static void show_file_pos(void)
+{
+	if (file_stack == NULL)
+		return;
+	
+	fprintf(stderr, "(%s:%d): ", file_stack -> fn, file_stack -> line);
+}
+
+void do_error(const char *f, ...)
+{
+	va_list arg;
+	
+	va_start(arg, f);
+	fprintf(stderr, "ERROR: ");
+	show_file_pos();
+	vfprintf(stderr, f, arg);
+	fprintf(stderr, "\n");
+	va_end(arg);
+	exit(1);
+}
+
+void do_warning(const char *f, ...)
+{
+	va_list arg;
+	
+	va_start(arg, f);
+	fprintf(stderr, "WARNING: ");
+	show_file_pos();
+	vfprintf(stderr, f, arg);
+	fprintf(stderr, "\n");
+	va_end(arg);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lwcc/cpp/file.c	Sun Sep 08 21:58:12 2013 -0600
@@ -0,0 +1,636 @@
+/*
+lwcc/cpp/file.c
+
+Copyright © 2013 William Astle
+
+This file is part of LWTOOLS.
+
+LWTOOLS is free software: you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later
+version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+NOTES:
+
+The function fetch_byte() grabs a byte from the input file. It returns
+CPP_EOF if end of file has been reached. The resulting byte has passed
+through three filters, in order:
+
+* All CRLF, LFCR, LF, and CR have been converted to CPP_EOL
+* If enabled (--trigraphs), trigraphs have been interpreted
+* \\n (backslash-newline) has been processed (eliminated)
+
+To obtain a byte without processing \\n, call fetch_byte_tg().
+
+*/
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h> 
+
+#include <lw_alloc.h>
+
+#include "cpp.h"
+
+struct file_stack_e *file_stack = NULL;
+
+int is_whitespace(int c)
+{
+	switch (c)
+	{
+	case ' ':
+	case '\t':
+	case '\r':
+	case '\n':
+		return 1;
+	}
+	return 0;
+}
+
+int is_sidchr(c)
+{
+	if (c == '_' || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))
+		return 1;
+	return 0;
+}
+
+int is_idchr(int c)
+{
+	if (c >= '0' && c <= '9')
+		return 1;
+	return is_sidchr(c);
+}
+
+int is_ep(int c)
+{
+	if (c == 'e' || c == 'E' || c == 'p' || c == 'P')
+		return 1;
+	return 0;
+}
+
+int is_hex(int c)
+{
+	if (c >= 'a' && c <= 'f')
+		return 1;
+	if (c >= 'A' && c <= 'F')
+		return 1;
+	if (c >= '0' && c <= '9')
+		return 1;
+	return 0;
+}
+
+int is_dec(int c)
+{
+	if (c >= '0' && c <= '9')
+		return 1;
+	return 0;
+}
+
+static void outchr(int c)
+{
+	fputc(c, output_fp);
+}
+
+static void outstr(char *s)
+{
+	while (*s)
+		outchr(*s++);
+}
+
+int fetch_byte_ll(struct file_stack_e *f)
+{
+	int c;
+
+	if (f -> eolstate != 0)	
+	{
+		f -> line++;
+		f -> col = 0;
+	}
+	c = getc(f -> fp);
+	f -> col++;
+	if (f -> eolstate == 1)
+	{
+		// just saw CR, munch LF
+		if (c == 10)
+			c = getc(f -> fp);
+		f -> eolstate = 0;
+	}
+	else if (f -> eolstate == 2)
+	{
+		// just saw LF, much CR
+		if (c == 13)
+			c = getc(f -> fp);
+		f -> eolstate = 0;
+	}
+	
+	if (c == 10)
+	{
+		// we have LF - end of line, flag to munch CR
+		f -> eolstate = 2;
+		c = CPP_EOL;
+	}
+	else if (c == 13)
+	{
+		// we have CR - end of line, flag to munch LF
+		f -> eolstate = 1;
+		c = CPP_EOL;
+	}
+	else if (c == EOF)
+	{
+		c = CPP_EOF;
+	}
+	return c;
+}
+
+int fetch_byte_tg(struct file_stack_e *f)
+{
+	int c;
+
+	if (!trigraphs)
+	{
+		c = fetch_byte_ll(f);
+	}
+	else
+	{
+		/* we have to do the trigraph shit here */
+		if (f -> ra != CPP_NOUNG)
+		{
+			if (f -> qseen > 0)
+			{
+				c = '?';
+				f -> qseen -= 1;
+				return c;
+			}
+			else
+			{
+				c = f -> ra;
+				f -> ra = CPP_NOUNG;
+				return c;
+			}
+		}
+	
+		c = fetch_byte_ll(f);
+		while (c == '?')
+		{
+			f -> qseen++;
+			c = fetch_byte_ll(f);
+		}
+	
+		if (f -> qseen >= 2)
+		{
+			// we have a trigraph
+			switch (c)
+			{
+			case '=':
+				c = '#';
+				f -> qseen -= 2;
+				break;
+			
+			case '/':
+				c = '\\';
+				f -> qseen -= 2;
+				break;
+		
+			case '\'':
+				c = '^';
+				f -> qseen -= 2;
+				break;
+		
+			case '(':
+				c = '[';
+				f -> qseen -= 2;
+				break;
+		
+			case ')':
+				c = ']';
+				f -> qseen -= 2;
+				break;
+		
+			case '!':
+				c = '|';
+				f -> qseen -= 2;
+				break;
+		
+			case '<':
+				c = '{';
+				f -> qseen -= 2;
+				break;
+		
+			case '>':
+				c = '}';
+				f -> qseen -= 2;
+				break;
+		
+			case '~':
+				c = '~';
+				f -> qseen -= 2;
+				break;
+			}
+			if (f -> qseen > 0)
+			{
+				f -> ra = c;
+				c = '?';
+				f -> qseen--;
+			}
+		}
+		else if (f -> qseen > 0)
+		{
+			f -> ra = c;
+			c = '?';
+			f -> qseen--;
+		}
+	}
+	return c;
+}
+
+int fetch_byte(struct file_stack_e *f)
+{
+	int c;
+	
+again:
+	if (f -> unget != CPP_NOUNG)
+	{
+		c = f -> unget;
+		f -> unget = CPP_NOUNG;
+	}
+	else
+	{
+		c = fetch_byte_tg(f);
+	}
+	if (c == '\\')
+	{
+		int c2;
+		c2 = fetch_byte_tg(f);
+		if (c2 == CPP_EOL)
+			goto again;
+		else
+			f -> unget = c2;
+	}
+	f -> curc = c;
+	return c;
+}
+
+static void skip_line(struct file_stack_e *f)
+{
+	int c;
+	while ((c = fetch_byte(f)) != CPP_EOL && c != CPP_EOF)
+		/* do nothing */ ;
+}
+
+
+struct
+{
+	char *name;
+	void (*fn)(struct file_stack_e *);
+} directives[] =
+{
+	{ NULL, NULL },
+	{ NULL, NULL }
+};
+
+/*
+This handles a preprocessing directive. Such a directive goes from the
+next character to be retrieved from f until the first instance of CPP_EOL
+or CPP_EOF.
+*/
+void handle_directive(struct file_stack_e *f)
+{
+	int c, i;
+	char kw[20];
+	
+again:
+	while ((c = fetch_byte(f)) == ' ' || c == '\t')
+		/* do nothing */ ;
+	if (c == '/')
+	{
+		// maybe a comment //
+		c = fetch_byte(f);
+		if (c == '/')
+		{
+			// line comment
+			skip_line(f);
+			return;
+		}
+		if (c == '*')
+		{
+			// block comment
+			while (1)
+			{
+				c = fetch_byte(f);
+				if (c == CPP_EOF)
+					return;
+				if (c == '*')
+				{
+					c = fetch_byte(f);
+					if (c == '/')
+					{
+						// end of comment - try again for directive
+						goto again;
+					}
+					if (c == CPP_EOF)
+						return;
+				}
+			}
+		}
+	}
+	
+	// empty directive - do nothing
+	if (c == CPP_EOL)
+		return;
+	
+	if (c < 'a' || c > 'z')
+		goto out;
+	
+	i = 0;
+	do
+	{
+		kw[i++] = c;
+		if (i == sizeof(kw) - 1)
+			goto out;	// keyword too long
+		c = fetch_byte(f);
+	} while ((c >= 'a' && c <= 'z') || (c == '_'));
+	kw[i++] = '\0';
+	
+	/* we have a keyword here */
+	for (i = 0; directives[i].name; i++)
+	{
+		if (strcmp(directives[i].name, kw) == 0)
+		{
+			(*directives[i].fn)(f);
+			return;
+		}
+	}
+
+/* if we fall through here, we have an unknown directive */
+out:
+	do_error("invalid preprocessor directive");
+	skip_line(f);
+}
+
+/*
+Notes:
+
+Rather than tokenize the entire file, we run through it interpreting
+things only as much as we need to in order to identify the following:
+
+preprocessing directives (#...)
+identifiers which might need to be replaced with macros
+
+We have to interpret strings, character constants, and numbers to prevent
+false positives in those situations.
+
+When we find a preprocessing directive, it is handled with a more
+aggressive tokenization process and then intepreted accordingly.
+
+nlws is used to record the fact that only whitespace has occurred at the
+start of a line. Whitespace is defined as comments or isspace(c). It gets
+reset to 1 after each EOL character. If a non-whitespace character is
+encountered, it is set to -1. If the character processing decides it really
+is a whitespace character, it will set nlws back to 1 (block comment).
+Elsewise, it will get set to 0 if it is still -1 when the loop starts again.
+
+This is needed so we can identify whitespace interposed before a
+preprocessor directive. This is the only case where it matters for
+the preprocessor.
+
+*/
+void preprocess_file(struct file_stack_e *f)
+{
+	int c;
+	int nlws = 1;
+	
+	while (1)
+	{
+		c = fetch_byte(f);
+again:
+		if (nlws == -1)
+			nlws = 0;
+		if (c == CPP_EOF)
+		{
+			outchr('\n');
+			return;
+		}
+		if (c == CPP_EOL)
+		{
+			nlws = 1;
+			outchr('\n');
+			continue;
+		}
+		
+		if (!is_whitespace(c))
+			nlws = -1;
+
+		if (is_sidchr(c))
+		{
+			// have identifier here - parse it off
+			char *ident = NULL;
+			int idlen = 0;
+			
+			do
+			{
+				ident = lw_realloc(ident, idlen + 1);
+				ident[idlen++] = c;
+				ident[idlen] = '\0';
+				c = fetch_byte(f);
+			} while (is_idchr(c));
+			
+			/* do something with the identifier here  - macros, etc. */
+			outstr(ident);
+			lw_free(ident);
+			
+			goto again;
+		}
+		
+		switch (c)
+		{
+		default:
+			outchr(c);
+			break;
+
+		case '.':	// a number - to prevent seeing an identifier in middle of number
+			outchr(c);
+			c = fetch_byte(f);
+			if (!is_dec(c))
+				goto again;
+			/* fall through */
+		case '0':
+		case '1':
+		case '2':
+		case '3':
+		case '4':
+		case '5':
+		case '6':
+		case '7':
+		case '8':
+		case '9':
+			do
+			{
+				outchr(c);
+				c = fetch_byte(f);
+				if (c == CPP_EOF)
+					return;
+				if (is_ep(c))
+				{
+					outchr(c);
+					c = fetch_byte(f);
+					if (c == '-' || c == '+')
+					{
+						outchr(c);
+						c = fetch_byte(f);
+					}
+				}
+			} while ((is_idchr(c)) || (c == '.'));
+			goto again;
+
+		case '#':
+			if (nlws)
+			{
+				handle_directive(f);
+				/* note: no need to reset nlws */
+			}
+			else
+				outchr('#');
+			break;
+		
+		case '\'':	// character constant
+			outchr('\'');
+			while ((c = fetch_byte(f)) != '\'')
+			{
+				if (c == '\\')
+				{
+					outchr('\\');
+					c = fetch_byte(f);
+				}
+				if (c == CPP_EOL)
+				{
+					do_warning("Unterminated character constant");
+					goto again;
+				}
+				if (c == CPP_EOF)
+					return;
+				outchr(c);
+			}
+			outchr(c);
+			break;
+			
+		case '"':	// strings
+			outchr(c);
+			while ((c = fetch_byte(f)) != '"')
+			{
+				if (c == '\\')
+				{
+					outchr('\\');
+					c = fetch_byte(f);
+				}
+				if (c == CPP_EOL)
+				{
+					do_warning("unterminated string literal");
+					goto again;
+				}
+				if (c == CPP_EOF)
+					return;
+				outchr(c);
+			}
+			outchr(c);
+			break;
+			
+		case '/':	// comments
+			c = fetch_byte(f);
+			if (c == '/')
+			{
+				// line comment
+				outchr(' ');
+				do
+				{
+					c = fetch_byte(f);
+				} while (c != CPP_EOF && c != CPP_EOL);
+			}
+			else if (c == '*')
+			{
+				// block comment
+				for (;;)
+				{
+					c = fetch_byte(f);
+					if (c == CPP_EOF)
+					{
+						break;
+					}
+					if (c == CPP_EOL)
+					{
+						continue;
+					}
+					if (c == '*')
+					{
+						// maybe end of comment
+						c = fetch_byte(f);
+						if (c == '/')
+						{
+							// end of comment
+							break;
+						}
+					}
+				}
+				// replace comment with a single space
+				outchr(' ');
+				if (nlws == -1)
+					nlws = 1;
+				continue;
+			}
+			else
+			{
+				// restore eaten '/'
+				outchr('/');
+				// process the character we just fetched
+				goto again;
+			}
+		} // switch
+	} // processing loop
+}
+
+int process_file(const char *f)
+{
+	struct file_stack_e *nf;
+	FILE *fp;
+
+	fprintf(stderr, "Processing %s\n", f);
+	
+	if (strcmp(f, "-") == 0)
+		fp = stdin;
+	else
+		fp = fopen(f, "rb");
+	if (fp == NULL)
+	{
+		do_warning("Cannot open %s: %s", f, strerror(errno));
+		return -1;
+	}
+
+	/* push the file onto the file stack */	
+	nf = lw_alloc(sizeof(struct file_stack_e));
+	nf -> fn = f;
+	nf -> fp = fp;
+	nf -> next = file_stack;
+	nf -> line = 1;
+	nf -> col = 0;
+	nf -> qseen = 0;
+	nf -> ra = CPP_NOUNG;
+	nf -> unget = CPP_NOUNG;
+	file_stack = nf;
+
+	/* go preprocess the file */
+	preprocess_file(nf);
+	
+	if (nf -> fp != stdin)
+		fclose(nf -> fp);
+	file_stack = nf -> next;
+	lw_free(nf);
+	return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lwcc/cpp/main.c	Sun Sep 08 21:58:12 2013 -0600
@@ -0,0 +1,134 @@
+/*
+lwcc/cpp/main.c
+
+Copyright © 2013 William Astle
+
+This file is part of LWTOOLS.
+
+LWTOOLS is free software: you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later
+version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <lw_stringlist.h>
+#include <lw_cmdline.h>
+
+#include "cpp.h"
+
+/* command line option handling */
+#define PROGVER "lwcc-cpp from " PACKAGE_STRING
+char *program_name;
+
+/* input files */
+lw_stringlist_t input_files;
+
+/* various flags */
+int trigraphs = 0;
+char *output_file = NULL;
+FILE *output_fp = NULL;
+
+static struct lw_cmdline_options options[] =
+{
+	{ "output",		'o',	"FILE",		0,							"Output to FILE"},
+	{ "include",	'i',	"FILE",		0,							"Pre-include FILE" },
+	{ "includedir",	'I',	"PATH",		0,							"Add entry to the user include path" },
+	{ "sincludedir", 'S',	"PATH",		0,							"Add entry to the system include path" },
+	{ "define", 	'D',	"SYM[=VAL]",0, 							"Automatically define SYM to be VAL (or 1)"},
+	{ "trigraphs",	0x100,	NULL,		0,							"Enable interpretation of trigraphs" },
+	{ 0 }
+};
+
+
+static int parse_opts(int key, char *arg, void *state)
+{
+	switch (key)
+	{
+	case 'o':
+		if (output_file)
+			do_error("Output file specified more than once.");
+		output_file = arg;
+		break;
+		
+	case 0x100:
+		trigraphs = 1;
+		break;
+		
+	case lw_cmdline_key_end:
+		break;
+	
+	case lw_cmdline_key_arg:
+		lw_stringlist_addstring(input_files, arg);
+		break;
+		
+	default:
+		return lw_cmdline_err_unknown;
+	}
+	return 0;
+}
+
+static struct lw_cmdline_parser cmdline_parser =
+{
+	options,
+	parse_opts,
+	"INPUTFILE",
+	"lwcc-cpp - C preprocessor for lwcc",
+	PROGVER
+};
+
+int main(int argc, char **argv)
+{
+	program_name = argv[0];
+	int retval = 0;
+	
+	input_files = lw_stringlist_create();
+
+	/* parse command line arguments */	
+	lw_cmdline_parse(&cmdline_parser, argc, argv, 0, 0, NULL);
+
+	/* set up output file */
+	if (output_file == NULL || strcmp(output_file, "-") == 0)
+	{
+		output_fp = stdout;
+	}
+	else
+	{
+		output_fp = fopen(output_file, "wb");
+		if (output_fp == NULL)
+		{
+			do_error("Failed to create output file %s: %s", output_file, strerror(errno)); 
+		}
+	}
+	
+	if (lw_stringlist_nstrings(input_files) == 0)
+	{
+		/* if no input files, work on stdin */
+		retval = process_file("-");
+	}
+	else
+	{
+		char *s;
+		lw_stringlist_reset(input_files);
+		for (s = lw_stringlist_current(input_files); s; s = lw_stringlist_next(input_files))
+		{
+			retval = process_file(s);
+			if (retval != 0)
+				break;
+		}
+	}
+	lw_stringlist_destroy(input_files);
+	exit(retval);
+}