diff --git a/Makefile b/Makefile index 50953be79cd7..8a3b3020df8e 100644 --- a/Makefile +++ b/Makefile @@ -311,7 +311,7 @@ ifeq ($(NODEP),1) $(C_BUILDDIR)/%.o: $(C_SUBDIR)/%.c ifeq (,$(KEEP_TEMPS)) @echo "$(CC1) -o $@ $<" - @$(CPP) $(CPPFLAGS) $< | $(PREPROC) $< charmap.txt -i | $(CC1) $(CFLAGS) -o - - | cat - <(echo -e ".text\n\t.align\t2, 0") | $(AS) $(ASFLAGS) -o $@ - + @$(CPP) $(CPPFLAGS) $< | $(PREPROC) -i $< charmap.txt | $(CC1) $(CFLAGS) -o - - | cat - <(echo -e ".text\n\t.align\t2, 0") | $(AS) $(ASFLAGS) -o $@ - else @$(CPP) $(CPPFLAGS) $< -o $(C_BUILDDIR)/$*.i @$(PREPROC) $(C_BUILDDIR)/$*.i charmap.txt | $(CC1) $(CFLAGS) -o $(C_BUILDDIR)/$*.s @@ -323,7 +323,7 @@ define C_DEP $1: $2 $$(shell $(SCANINC) -I include -I tools/agbcc/include -I gflib $2) ifeq (,$$(KEEP_TEMPS)) @echo "$$(CC1) -o $$@ $$<" - @$$(CPP) $$(CPPFLAGS) $$< | $$(PREPROC) $$< charmap.txt -i | $$(CC1) $$(CFLAGS) -o - - | cat - <(echo -e ".text\n\t.align\t2, 0") | $$(AS) $$(ASFLAGS) -o $$@ - + @$$(CPP) $$(CPPFLAGS) $$< | $$(PREPROC) -i $$< charmap.txt | $$(CC1) $$(CFLAGS) -o - - | cat - <(echo -e ".text\n\t.align\t2, 0") | $$(AS) $$(ASFLAGS) -o $$@ - else @$$(CPP) $$(CPPFLAGS) $$< -o $$(C_BUILDDIR)/$3.i @$$(PREPROC) $$(C_BUILDDIR)/$3.i charmap.txt | $$(CC1) $$(CFLAGS) -o $$(C_BUILDDIR)/$3.s @@ -338,7 +338,7 @@ ifeq ($(NODEP),1) $(GFLIB_BUILDDIR)/%.o: $(GFLIB_SUBDIR)/%.c $$(c_dep) ifeq (,$(KEEP_TEMPS)) @echo "$(CC1) -o $@ $<" - @$(CPP) $(CPPFLAGS) $< | $(PREPROC) $< charmap.txt -i | $(CC1) $(CFLAGS) -o - - | cat - <(echo -e ".text\n\t.align\t2, 0") | $(AS) $(ASFLAGS) -o $@ - + @$(CPP) $(CPPFLAGS) $< | $(PREPROC) -i $< charmap.txt | $(CC1) $(CFLAGS) -o - - | cat - <(echo -e ".text\n\t.align\t2, 0") | $(AS) $(ASFLAGS) -o $@ - else @$(CPP) $(CPPFLAGS) $< -o $(GFLIB_BUILDDIR)/$*.i @$(PREPROC) $(GFLIB_BUILDDIR)/$*.i charmap.txt | $(CC1) $(CFLAGS) -o $(GFLIB_BUILDDIR)/$*.s @@ -350,7 +350,7 @@ define GFLIB_DEP $1: $2 $$(shell $(SCANINC) -I include -I tools/agbcc/include -I gflib $2) ifeq (,$$(KEEP_TEMPS)) @echo "$$(CC1) -o $$@ $$<" - @$$(CPP) $$(CPPFLAGS) $$< | $$(PREPROC) $$< charmap.txt -i | $$(CC1) $$(CFLAGS) -o - - | cat - <(echo -e ".text\n\t.align\t2, 0") | $$(AS) $$(ASFLAGS) -o $$@ - + @$$(CPP) $$(CPPFLAGS) $$< | $$(PREPROC) -i $$< charmap.txt | $$(CC1) $$(CFLAGS) -o - - | cat - <(echo -e ".text\n\t.align\t2, 0") | $$(AS) $$(ASFLAGS) -o $$@ - else @$$(CPP) $$(CPPFLAGS) $$< -o $$(GFLIB_BUILDDIR)/$3.i @$$(PREPROC) $$(GFLIB_BUILDDIR)/$3.i charmap.txt | $$(CC1) $$(CFLAGS) -o $$(GFLIB_BUILDDIR)/$3.s @@ -363,11 +363,11 @@ endif ifeq ($(NODEP),1) $(C_BUILDDIR)/%.o: $(C_SUBDIR)/%.s - $(PREPROC) $< charmap.txt | $(CPP) -I include - | $(AS) $(ASFLAGS) -o $@ + $(PREPROC) $< charmap.txt | $(CPP) -I include - | $(PREPROC) -i $$< charmap.txt | $(AS) $(ASFLAGS) -o $@ else define SRC_ASM_DATA_DEP $1: $2 $$(shell $(SCANINC) -I include -I "" $2) - $$(PREPROC) $$< charmap.txt | $$(CPP) -I include - | $$(AS) $$(ASFLAGS) -o $$@ + $$(PREPROC) $$< charmap.txt | $$(CPP) -I include - | $$(PREPROC) -ie $$< charmap.txt | $$(AS) $$(ASFLAGS) -o $$@ endef $(foreach src, $(C_ASM_SRCS), $(eval $(call SRC_ASM_DATA_DEP,$(patsubst $(C_SUBDIR)/%.s,$(C_BUILDDIR)/%.o, $(src)),$(src)))) endif @@ -385,7 +385,7 @@ endif ifeq ($(NODEP),1) $(DATA_ASM_BUILDDIR)/%.o: $(DATA_ASM_SUBDIR)/%.s - $(PREPROC) $< charmap.txt | $(CPP) -I include - | $(AS) $(ASFLAGS) -o $@ + $(PREPROC) $< charmap.txt | $(CPP) -I include - | $(PREPROC) -ie $$< charmap.txt | $(AS) $(ASFLAGS) -o $@ else $(foreach src, $(REGULAR_DATA_ASM_SRCS), $(eval $(call SRC_ASM_DATA_DEP,$(patsubst $(DATA_ASM_SUBDIR)/%.s,$(DATA_ASM_BUILDDIR)/%.o, $(src)),$(src)))) endif diff --git a/tools/preproc/Makefile b/tools/preproc/Makefile index 1507c973f27e..606318944c63 100644 --- a/tools/preproc/Makefile +++ b/tools/preproc/Makefile @@ -3,10 +3,10 @@ CXX ?= g++ CXXFLAGS := -std=c++11 -O2 -Wall -Wno-switch -Werror SRCS := asm_file.cpp c_file.cpp charmap.cpp preproc.cpp string_parser.cpp \ - utf8.cpp + utf8.cpp io.cpp HEADERS := asm_file.h c_file.h char_util.h charmap.h preproc.h string_parser.h \ - utf8.h + utf8.h io.h ifeq ($(OS),Windows_NT) EXE := .exe diff --git a/tools/preproc/asm_file.cpp b/tools/preproc/asm_file.cpp index 04a7410e00bf..ca8b8cc4ae96 100644 --- a/tools/preproc/asm_file.cpp +++ b/tools/preproc/asm_file.cpp @@ -27,33 +27,12 @@ #include "utf8.h" #include "string_parser.h" #include "../../gflib/characters.h" +#include "io.h" -AsmFile::AsmFile(std::string filename) : m_filename(filename) +AsmFile::AsmFile(std::string filename, bool isStdin, bool doEnum) : m_filename(filename) { - FILE *fp = std::fopen(filename.c_str(), "rb"); - - if (fp == NULL) - FATAL_ERROR("Failed to open \"%s\" for reading.\n", filename.c_str()); - - std::fseek(fp, 0, SEEK_END); - - m_size = std::ftell(fp); - - if (m_size < 0) - FATAL_ERROR("File size of \"%s\" is less than zero.\n", filename.c_str()); - else if (m_size == 0) - return; // Empty file - - m_buffer = new char[m_size + 1]; - - std::rewind(fp); - - if (std::fread(m_buffer, m_size, 1, fp) != 1) - FATAL_ERROR("Failed to read \"%s\".\n", filename.c_str()); - - m_buffer[m_size] = 0; - - std::fclose(fp); + m_buffer = ReadFileToBuffer(filename.c_str(), isStdin, &m_size); + m_doEnum = doEnum; m_pos = 0; m_lineNum = 1; @@ -65,6 +44,7 @@ AsmFile::AsmFile(std::string filename) : m_filename(filename) AsmFile::AsmFile(AsmFile&& other) : m_filename(std::move(other.m_filename)) { m_buffer = other.m_buffer; + m_doEnum = other.m_doEnum; m_pos = other.m_pos; m_size = other.m_size; m_lineNum = other.m_lineNum; @@ -174,6 +154,8 @@ Directive AsmFile::GetDirective() return Directive::String; else if (CheckForDirective(".braille")) return Directive::Braille; + else if (CheckForDirective("enum")) + return Directive::Enum; else return Directive::Unknown; } @@ -527,6 +509,70 @@ void AsmFile::OutputLine() } } +// parses an assumed C `enum`. Returns false if `enum { ...` is not matched +bool AsmFile::ParseEnum() +{ + if (!m_doEnum) + return false; + + long fallbackPosition = m_pos; + std::string headerFilename = ""; + long currentHeaderLine = SkipWhitespaceAndEol(); + std::string enumName = ReadIdentifier(); + currentHeaderLine += SkipWhitespaceAndEol(); + long enumCounter = 0; + long symbolCount = 0; + + if (m_buffer[m_pos] != '{') // assume assembly macro, otherwise assume enum and report errors accordingly + { + m_pos = fallbackPosition - 4; + return false; + } + + currentHeaderLine += FindLastLineNumber(headerFilename); + m_pos++; + for (;;) + { + currentHeaderLine += SkipWhitespaceAndEol(); + std::string currentIdentName = ReadIdentifier(); + if (!currentIdentName.empty()) + { + std::printf("# %ld \"%s\"\n", currentHeaderLine, headerFilename.c_str()); + currentHeaderLine += SkipWhitespaceAndEol(); + if (m_buffer[m_pos] == '=') + { + m_pos++; + currentHeaderLine += SkipWhitespaceAndEol(); + enumCounter = ReadInteger(headerFilename, currentHeaderLine); + currentHeaderLine += SkipWhitespaceAndEol(); + } + std::printf(".equiv %s, %ld\n", currentIdentName.c_str(), enumCounter); + enumCounter++; + symbolCount++; + } + else if (symbolCount == 0) + { + RaiseError("%s:%ld: empty enum is invalid", headerFilename.c_str(), currentHeaderLine); + } + + if (m_buffer[m_pos] != ',') + { + currentHeaderLine += SkipWhitespaceAndEol(); + if (m_buffer[m_pos++] == '}' && m_buffer[m_pos++] == ';') + { + ExpectEmptyRestOfLine(); + break; + } + else + { + RaiseError("unterminated enum from included file %s:%ld", headerFilename.c_str(), currentHeaderLine); + } + } + m_pos++; + } + return true; +} + // Asserts that the rest of the line is empty and moves to the next one. void AsmFile::ExpectEmptyRestOfLine() { @@ -599,3 +645,130 @@ void AsmFile::RaiseWarning(const char* format, ...) { DO_REPORT("warning"); } + +// Skips Whitespace including newlines and returns the amount of newlines skipped +int AsmFile::SkipWhitespaceAndEol() +{ + int newlines = 0; + while (m_buffer[m_pos] == '\t' || m_buffer[m_pos] == ' ' || m_buffer[m_pos] == '\n') + { + if (m_buffer[m_pos] == '\n') + newlines++; + m_pos++; + } + return newlines; +} + +// returns the last line indicator and its corresponding file name without modifying the token index +int AsmFile::FindLastLineNumber(std::string& filename) +{ + long pos = m_pos; + long linebreaks = 0; + while (m_buffer[pos] != '#' && pos >= 0) + { + if (m_buffer[pos] == '\n') + linebreaks++; + pos--; + } + + if (pos < 0) + RaiseError("line indicator for header file not found before `enum`"); + + pos++; + while (m_buffer[pos] == ' ' || m_buffer[pos] == '\t') + pos++; + + if (!IsAsciiDigit(m_buffer[pos])) + RaiseError("malformatted line indicator found before `enum`, expected line number"); + + unsigned n = 0; + int digit = 0; + while ((digit = ConvertDigit(m_buffer[pos++], 10)) != -1) + n = 10 * n + digit; + + while (m_buffer[pos] == ' ' || m_buffer[pos] == '\t') + pos++; + + if (m_buffer[pos++] != '"') + RaiseError("malformatted line indicator found before `enum`, expected filename"); + + while (m_buffer[pos] != '"') + { + unsigned char c = m_buffer[pos++]; + + if (c == 0) + { + if (pos >= m_size) + RaiseError("unexpected EOF in line indicator"); + else + RaiseError("unexpected null character in line indicator"); + } + + if (!IsAsciiPrintable(c)) + RaiseError("unexpected character '\\x%02X' in line indicator", c); + + if (c == '\\') + { + c = m_buffer[pos]; + RaiseError("unexpected escape '\\%c' in line indicator", c); + } + + filename += c; + } + + return n + linebreaks - 1; +} + +std::string AsmFile::ReadIdentifier() +{ + long start = m_pos; + if (!IsIdentifierStartingChar(m_buffer[m_pos])) + return std::string(); + + m_pos++; + + while (IsIdentifierChar(m_buffer[m_pos])) + m_pos++; + + return std::string(&m_buffer[start], m_pos - start); +} + +long AsmFile::ReadInteger(std::string filename, long line) +{ + bool negate = false; + int radix = 10; + if (!IsAsciiDigit(m_buffer[m_pos])) + { + if (m_buffer[m_pos++] == '-') + negate = true; + else + RaiseError("expected number in included file %s:%ld", filename.c_str(), line); + } + + if (m_buffer[m_pos] == '0' && m_buffer[m_pos + 1] == 'x') + { + radix = 16; + m_pos += 2; + } + else if (m_buffer[m_pos] == '0' && m_buffer[m_pos + 1] == 'b') + { + radix = 2; + m_pos += 2; + } + else if (m_buffer[m_pos] == '0' && IsAsciiDigit(m_buffer[m_pos+1])) + { + radix = 8; + m_pos++; + } + + long n = 0; + int digit; + + while ((digit = ConvertDigit(m_buffer[m_pos], radix)) != -1) + { + n = n * radix + digit; + m_pos++; + } + + return negate ? -n : n; +} diff --git a/tools/preproc/asm_file.h b/tools/preproc/asm_file.h index 29435f76a47a..33e6ce5c49c1 100644 --- a/tools/preproc/asm_file.h +++ b/tools/preproc/asm_file.h @@ -31,13 +31,14 @@ enum class Directive Include, String, Braille, + Enum, Unknown }; class AsmFile { public: - AsmFile(std::string filename); + AsmFile(std::string filename, bool isStdin, bool doEnum); AsmFile(AsmFile&& other); AsmFile(const AsmFile&) = delete; ~AsmFile(); @@ -49,9 +50,11 @@ class AsmFile bool IsAtEnd(); void OutputLine(); void OutputLocation(); + bool ParseEnum(); private: char* m_buffer; + bool m_doEnum; long m_pos; long m_size; long m_lineNum; @@ -68,6 +71,10 @@ class AsmFile void RaiseError(const char* format, ...); void RaiseWarning(const char* format, ...); void VerifyStringLength(int length); + int SkipWhitespaceAndEol(); + int FindLastLineNumber(std::string& filename); + std::string ReadIdentifier(); + long ReadInteger(std::string filename, long line); }; #endif // ASM_FILE_H diff --git a/tools/preproc/c_file.cpp b/tools/preproc/c_file.cpp index 508c6287313a..191ca4c152e6 100644 --- a/tools/preproc/c_file.cpp +++ b/tools/preproc/c_file.cpp @@ -30,56 +30,16 @@ #include "char_util.h" #include "utf8.h" #include "string_parser.h" +#include "io.h" CFile::CFile(const char * filenameCStr, bool isStdin) { - FILE *fp; - - if (isStdin) { - fp = stdin; + if (isStdin) m_filename = std::string{"/"}.append(filenameCStr); - } else { - fp = std::fopen(filenameCStr, "rb"); + else m_filename = std::string(filenameCStr); - } - - std::string& filename = m_filename; - - if (fp == NULL) - FATAL_ERROR("Failed to open \"%s\" for reading.\n", filename.c_str()); - - m_size = 0; - m_buffer = (char *)malloc(CHUNK_SIZE + 1); - if (m_buffer == NULL) { - FATAL_ERROR("Failed to allocate memory to process file \"%s\"!", filename.c_str()); - } - - std::size_t numAllocatedBytes = CHUNK_SIZE + 1; - std::size_t bufferOffset = 0; - std::size_t count; - - while ((count = std::fread(m_buffer + bufferOffset, 1, CHUNK_SIZE, fp)) != 0) { - if (!std::ferror(fp)) { - m_size += count; - if (std::feof(fp)) { - break; - } - - numAllocatedBytes += CHUNK_SIZE; - bufferOffset += CHUNK_SIZE; - m_buffer = (char *)realloc(m_buffer, numAllocatedBytes); - if (m_buffer == NULL) { - FATAL_ERROR("Failed to allocate memory to process file \"%s\"!", filename.c_str()); - } - } else { - FATAL_ERROR("Failed to read \"%s\". (error: %s)", filename.c_str(), std::strerror(errno)); - } - } - - m_buffer[m_size] = 0; - - std::fclose(fp); + m_buffer = ReadFileToBuffer(filenameCStr, isStdin, &m_size); m_pos = 0; m_lineNum = 1; diff --git a/tools/preproc/c_file.h b/tools/preproc/c_file.h index 49e633a18dd4..c40c33c96277 100644 --- a/tools/preproc/c_file.h +++ b/tools/preproc/c_file.h @@ -56,6 +56,4 @@ class CFile void RaiseWarning(const char* format, ...); }; -#define CHUNK_SIZE 4096 - #endif // C_FILE_H diff --git a/tools/preproc/io.cpp b/tools/preproc/io.cpp new file mode 100644 index 000000000000..321676180d70 --- /dev/null +++ b/tools/preproc/io.cpp @@ -0,0 +1,51 @@ +#include "preproc.h" +#include "io.h" +#include +#include +#include + +char *ReadFileToBuffer(const char *filename, bool isStdin, long *size) +{ + FILE *fp; + if (isStdin) + fp = stdin; + else + fp = std::fopen(filename, "rb"); + + if (fp == NULL) + FATAL_ERROR("Failed to open \"%s\" for reading.\n", filename); + + *size = 0; + char *buffer = (char *)malloc(CHUNK_SIZE + 1); + if (buffer == NULL) { + FATAL_ERROR("Failed to allocate memory to process file \"%s\"!", filename); + } + + std::size_t numAllocatedBytes = CHUNK_SIZE + 1; + std::size_t bufferOffset = 0; + std::size_t count; + + while ((count = std::fread(buffer + bufferOffset, 1, CHUNK_SIZE, fp)) != 0) { + if (!std::ferror(fp)) { + *size += count; + + if (std::feof(fp)) { + break; + } + + numAllocatedBytes += CHUNK_SIZE; + bufferOffset += CHUNK_SIZE; + buffer = (char *)realloc(buffer, numAllocatedBytes); + if (buffer == NULL) { + FATAL_ERROR("Failed to allocate memory to process file \"%s\"!", filename); + } + } else { + FATAL_ERROR("Failed to read \"%s\". (error: %s)", filename, std::strerror(errno)); + } + } + + buffer[*size] = 0; + + std::fclose(fp); + return buffer; +} diff --git a/tools/preproc/io.h b/tools/preproc/io.h new file mode 100644 index 000000000000..ac4e91051f1c --- /dev/null +++ b/tools/preproc/io.h @@ -0,0 +1,8 @@ +#ifndef IO_H_ +#define IO_H_ + +#define CHUNK_SIZE 4096 + +char *ReadFileToBuffer(const char *filename, bool isStdin, long *size); + +#endif // IO_H_ diff --git a/tools/preproc/preproc.cpp b/tools/preproc/preproc.cpp index eb2d4c8a23f7..20c2de51b635 100644 --- a/tools/preproc/preproc.cpp +++ b/tools/preproc/preproc.cpp @@ -20,11 +20,14 @@ #include #include +#include #include "preproc.h" #include "asm_file.h" #include "c_file.h" #include "charmap.h" +static void UsageAndExit(const char *program); + Charmap* g_charmap; void PrintAsmBytes(unsigned char *s, int length) @@ -43,11 +46,12 @@ void PrintAsmBytes(unsigned char *s, int length) } } -void PreprocAsmFile(std::string filename) +void PreprocAsmFile(std::string filename, bool isStdin, bool doEnum) { std::stack stack; - stack.push(AsmFile(filename)); + stack.push(AsmFile(filename, isStdin, doEnum)); + std::printf("# 1 \"%s\"\n", filename.c_str()); for (;;) { @@ -66,7 +70,7 @@ void PreprocAsmFile(std::string filename) switch (directive) { case Directive::Include: - stack.push(AsmFile(stack.top().ReadPath())); + stack.push(AsmFile(stack.top().ReadPath(), false, doEnum)); stack.top().OutputLocation(); break; case Directive::String: @@ -83,6 +87,12 @@ void PreprocAsmFile(std::string filename) PrintAsmBytes(s, length); break; } + case Directive::Enum: + { + if (!stack.top().ParseEnum()) + stack.top().OutputLine(); + break; + } case Directive::Unknown: { std::string globalLabel = stack.top().GetGlobalLabel(); @@ -109,9 +119,9 @@ void PreprocCFile(const char * filename, bool isStdin) cFile.Preproc(); } -char* GetFileExtension(char* filename) +const char* GetFileExtension(const char* filename) { - char* extension = filename; + const char* extension = filename; while (*extension != 0) extension++; @@ -130,35 +140,64 @@ char* GetFileExtension(char* filename) return extension; } +static void UsageAndExit(const char *program) +{ + std::fprintf(stderr, "Usage: %s [-i] [-e] SRC_FILE CHARMAP_FILE\nwhere -i denotes if input is from stdin\n -e enables enum handling\n", program); + std::exit(EXIT_FAILURE); +} + int main(int argc, char **argv) { - if (argc < 3 || argc > 4) + int opt; + const char *source = NULL; + const char *charmap = NULL; + bool isStdin = false; + bool doEnum = false; + + /* preproc [-i] [-e] SRC_FILE CHARMAP_FILE */ + while ((opt = getopt(argc, argv, "ie")) != -1) { - std::fprintf(stderr, "Usage: %s SRC_FILE CHARMAP_FILE [-i]\nwhere -i denotes if input is from stdin\n", argv[0]); - return 1; + switch (opt) + { + case 'i': + isStdin = true; + break; + case 'e': + doEnum = true; + break; + default: + UsageAndExit(argv[0]); + break; + } } - g_charmap = new Charmap(argv[2]); + if (optind + 2 != argc) + UsageAndExit(argv[0]); + + source = argv[optind + 0]; + charmap = argv[optind + 1]; + + g_charmap = new Charmap(charmap); - char* extension = GetFileExtension(argv[1]); + const char* extension = GetFileExtension(source); if (!extension) FATAL_ERROR("\"%s\" has no file extension.\n", argv[1]); if ((extension[0] == 's') && extension[1] == 0) - PreprocAsmFile(argv[1]); - else if ((extension[0] == 'c' || extension[0] == 'i') && extension[1] == 0) { - if (argc == 4) { - if (argv[3][0] == '-' && argv[3][1] == 'i' && argv[3][2] == '\0') { - PreprocCFile(argv[1], true); - } else { - FATAL_ERROR("unknown argument flag \"%s\".\n", argv[3]); - } - } else { - PreprocCFile(argv[1], false); - } - } else + { + PreprocAsmFile(source, isStdin, doEnum); + } + else if ((extension[0] == 'c' || extension[0] == 'i') && extension[1] == 0) + { + if (doEnum) + FATAL_ERROR("-e is invalid for C sources\n"); + PreprocCFile(source, isStdin); + } + else + { FATAL_ERROR("\"%s\" has an unknown file extension of \"%s\".\n", argv[1], extension); + } return 0; }