diff --git a/CMakeLists.txt b/CMakeLists.txt index 67f1b96c41..f46cdec399 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,4 +69,5 @@ add_subdirectory(Launcher) else() add_subdirectory(MakeAPPL) add_subdirectory(ASFilter) +add_subdirectory(Rez) endif() diff --git a/MakeAPPL/BinaryIO.cc b/MakeAPPL/BinaryIO.cc new file mode 100644 index 0000000000..b8bf73b3ec --- /dev/null +++ b/MakeAPPL/BinaryIO.cc @@ -0,0 +1,50 @@ +#include "BinaryIO.h" +#include +#include + +#include "ResType.h" + +void byte(std::ostream& out, int byte) +{ + out.put((unsigned char)byte); +} +void word(std::ostream& out, int word) +{ + byte(out,(word >> 8) & 0xFF); + byte(out,word & 0xFF); +} +void ostype(std::ostream& out, ResType type) +{ + longword(out, type); +} +void longword(std::ostream& out, int longword) +{ + byte(out,(longword >> 24) & 0xFF); + byte(out,(longword >> 16) & 0xFF); + byte(out,(longword >> 8) & 0xFF); + byte(out,longword & 0xFF); +} + +int byte(std::istream& in) +{ + return in.get() & 0xFF; +} +int word(std::istream& in) +{ + int a = byte(in); + int b = byte(in); + return (a << 8) | b; +} +ResType ostype(std::istream& in) +{ + return longword(in); +} +int longword(std::istream& in) +{ + int a = byte(in); + int b = byte(in); + int c = byte(in); + int d = byte(in); + return (a << 24) | (b << 16) | (c << 8) | d; +} + diff --git a/MakeAPPL/BinaryIO.h b/MakeAPPL/BinaryIO.h new file mode 100644 index 0000000000..a23f004bfa --- /dev/null +++ b/MakeAPPL/BinaryIO.h @@ -0,0 +1,19 @@ +#ifndef BINARYIO_H +#define BINARYIO_H + +#include +#include + +class ResType; + +void byte(std::ostream& out, int byte); +void word(std::ostream& out, int word); +void ostype(std::ostream& out, ResType type); +void longword(std::ostream& out, int longword); + +int byte(std::istream& in); +int word(std::istream& in); +ResType ostype(std::istream& in); +int longword(std::istream& in); + +#endif // BINARYIO_H diff --git a/MakeAPPL/CMakeLists.txt b/MakeAPPL/CMakeLists.txt index 8bd5489b1e..11347f2356 100644 --- a/MakeAPPL/CMakeLists.txt +++ b/MakeAPPL/CMakeLists.txt @@ -16,7 +16,11 @@ # along with Retro68. If not, see . cmake_minimum_required(VERSION 2.8) +set(CMAKE_CXX_FLAGS "--std=c++0x") +add_library(ResourceFiles ResourceFiles.h ResourceFiles.cc BinaryIO.h BinaryIO.cc ResType.h ResType.cc) +target_include_directories(ResourceFiles PUBLIC .) add_executable(MakeAPPL main.cc) +target_link_libraries(MakeAPPL ResourceFiles) install(TARGETS MakeAPPL RUNTIME DESTINATION bin) diff --git a/MakeAPPL/ResType.cc b/MakeAPPL/ResType.cc new file mode 100644 index 0000000000..87ece75f3e --- /dev/null +++ b/MakeAPPL/ResType.cc @@ -0,0 +1,58 @@ +#include "ResType.h" +#include +#include + +ResType::ResType(const std::string &str) +{ + auto p = str.begin(); + auto e = str.end(); + + assert(str.size() == 4); + + x = 0; + while(p != e) + { + x <<= 8; + x |= (*p) & 0xFF; + ++p; + } +} + +ResType::ResType(const char *s) +{ + auto p = s; + auto e = s + 4; + + assert(s[0] && s[1] && s[2] && s[3] && !s[4]); + + x = 0; + while(p != e) + { + x <<= 8; + x |= (*p) & 0xFF; + ++p; + } +} + + +ResType::operator std::string() +{ + char c1 = static_cast(x >> 24); + char c2 = static_cast(x >> 16); + char c3 = static_cast(x >> 8); + char c4 = static_cast(x); + + return std::string{ c1, c2, c3, c4 }; +} + +std::ostream &operator<<(std::ostream &out, ResType t) +{ + char c1 = static_cast((int)t >> 24); + char c2 = static_cast((int)t >> 16); + char c3 = static_cast((int)t >> 8); + char c4 = static_cast((int)t); + + out << "'" << c1 << c2 << c3 << c4 << "'"; + return out; +} + diff --git a/MakeAPPL/ResType.h b/MakeAPPL/ResType.h new file mode 100644 index 0000000000..0b44d48e75 --- /dev/null +++ b/MakeAPPL/ResType.h @@ -0,0 +1,35 @@ +#ifndef RESTYPE_H +#define RESTYPE_H + +#include +#include + +class ResType +{ + int x; +public: + ResType() : x(0) {} + ResType(int x) : x(x) {} + ResType(const std::string& s); + ResType(const char* s); + + operator int() const { return x; } + bool operator<(ResType y) const { return x < y.x; } + + operator std::string(); +}; + +std::ostream& operator<<(std::ostream& out, ResType t); + +struct ResRef : public std::pair +{ + ResRef() : std::pair(ResType(), 0) {} + ResRef(ResType t, int id) : std::pair(t,id) {} + + ResType& type() { return first; } + ResType type() const { return first; } + int& id() { return second; } + int id() const { return second; } +}; + +#endif // RESTYPE_H diff --git a/MakeAPPL/ResourceFiles.cc b/MakeAPPL/ResourceFiles.cc new file mode 100644 index 0000000000..ad6ce0ca1c --- /dev/null +++ b/MakeAPPL/ResourceFiles.cc @@ -0,0 +1,133 @@ +#include "ResourceFiles.h" +#include "BinaryIO.h" + +#include +#include +#include + +void Resources::addResources(const Resources& res) +{ + for(auto& rr : res.resources) + resources.insert(rr); +// resources.insert(resources.end(),res.resources.begin(), res.resources.end()); +} + +void Resources::writeFork(std::ostream& out) const +{ + std::streampos start = out.tellp(); + longword(out,0x100); + longword(out,0); + longword(out,0); + longword(out,0); + out.seekp(start + std::streampos(0x100)); + std::map< std::string, std::map > resourceInfos; + std::streampos datastart = out.tellp(); + for(auto& rr : resources) + { + const Resource& r = rr.second; + const std::string& data = r.getData(); + resourceInfos[ r.getType() ][ r.getID() ] = out.tellp() - datastart; + longword(out, data.size()); + out << data; + } + std::streampos dataend = out.tellp(); +// while(out.tellp() % 0x100) +// out.put(0); + std::streampos resmap = out.tellp(); + out.seekp(16+4+2+2, std::ios::cur); + word(out,16+4+2+2+2+2); // offset to resource type list + std::streampos resnameOffset = out.tellp(); + word(out,0); + std::streampos typelist = out.tellp(); + word(out,resourceInfos.size() - 1); + for(std::map< std::string, std::map >::iterator p = resourceInfos.begin(); + p != resourceInfos.end(); ++p) + { + if(p->second.size()) + { + ostype(out,p->first); + word(out,p->second.size()-1); + word(out,0); // replaced later + } + } + int typeIndex = 0; + for(std::map< std::string, std::map >::iterator p = resourceInfos.begin(); + p != resourceInfos.end(); ++p) + { + if(p->second.size()) + { + std::streampos pos = out.tellp(); + out.seekp((int)typelist + 2 + 8 * typeIndex + 6); + word(out, pos - typelist); + out.seekp(pos); + typeIndex++; + + for(std::map::iterator q = p->second.begin(); q != p->second.end(); ++q) + { + word(out,q->first); + word(out,-1); + longword(out,q->second); + longword(out,0); + } + } + } + std::streampos resnames = out.tellp(); + out.seekp(resnameOffset); + word(out, resnames - resmap); + out.seekp(resnames); + std::streampos end = out.tellp(); + out.seekp(start + std::streampos(4)); + longword(out, resmap - start); + longword(out, dataend - start - std::streampos(0x100)); + longword(out, end - resmap); + out.seekp(end); +} + +Resources::Resources(std::istream &in) +{ + std::streampos start = in.tellg(); + int resdataOffset = longword(in); + int resmapOffset = longword(in); + + in.seekg(start + std::streampos(resmapOffset + 16 + 4 + 2 + 2)); + int typeListOffset = word(in); + int nameListOffset = word(in); + int nTypes = (word(in) + 1) & 0xFFFF; + + for(int i = 0; i < nTypes; i++) + { + in.seekg(start + std::streampos(resmapOffset + typeListOffset + 2 + i * 8)); + std::string type = ostype(in); + int nRes = (word(in) + 1) & 0xFFFF; + int refListOffset = word(in); + + for(int j = 0; j < nRes; j++) + { + in.seekg(start + std::streampos(resmapOffset + typeListOffset + refListOffset + j * 12)); + int id = word(in); + int nameOffset = word(in); + int attr = byte(in); + int off1 = byte(in); + int off2 = byte(in); + int off3 = byte(in); + int offset = (off1 << 16) | (off2 << 8) | off3; + std::string name; + if(nameOffset != 0xFFFF) + { + in.seekg(start + std::streampos(resmapOffset + nameListOffset + nameOffset)); + int nameLen = byte(in); + char buf[256]; + in.read(buf, nameLen); + name = std::string(buf, nameLen); + } + + in.seekg(start + std::streampos(resdataOffset + offset)); + int size = longword(in); + std::vector tmp(size); + in.read(tmp.data(), size); + std::string data(tmp.data(), size); + + addResource(Resource(type, id, data, name, attr)); + } + } +} diff --git a/MakeAPPL/ResourceFiles.h b/MakeAPPL/ResourceFiles.h new file mode 100644 index 0000000000..308df74d1d --- /dev/null +++ b/MakeAPPL/ResourceFiles.h @@ -0,0 +1,44 @@ +#ifndef RESOURCEFILES_H +#define RESOURCEFILES_H + +#include +#include +#include "ResType.h" + +class Resource +{ + ResType type; + int id; + std::string name; + std::string data; + int attr; +public: + Resource() {} + Resource(ResType type, int id, std::string data, std::string name = "", int attr = 0) + : type(type), id(id), name(name), data(data), attr(attr) {} + + const std::string& getData() const { return data; } + inline ResType getType() const { return type; } + inline int getID() const { return id; } + inline ResRef getTypeAndID() const { return ResRef(type, id); } +}; + +class Fork +{ +public: + virtual void writeFork(std::ostream& out) const { } + virtual ~Fork() {} +}; + +class Resources : public Fork +{ + std::map resources; +public: + Resources() {} + Resources(std::istream& in); + void writeFork(std::ostream& out) const; + void addResource(Resource res) { resources[res.getTypeAndID()] = res; } + void addResources(const Resources& res); +}; + +#endif // RESOURCEFILES_H diff --git a/MakeAPPL/main.cc b/MakeAPPL/main.cc index 7a2ae6dfde..9128577d5b 100644 --- a/MakeAPPL/main.cc +++ b/MakeAPPL/main.cc @@ -31,6 +31,8 @@ #ifdef __APPLE__ #include #endif +#include "ResourceFiles.h" +#include "BinaryIO.h" std::string commandPath; @@ -49,212 +51,6 @@ void wrapMacBinary(std::string macBinaryFile, std::string diskImagePath) std::system((commandPath + "hcopy -m " + macBinaryFile + " :").c_str()); } -class Resource -{ - std::string type; - int id; - std::string name; - std::string data; - int attr; -public: - Resource(std::string type, int id, std::string data, std::string name = "", int attr = 0) - : type(type), id(id), data(data), name(name), attr(attr) {} - - const std::string& getData() const { return data; } - inline std::string getType() const { return type; } - inline int getID() const { return id; } -}; - -class Fork -{ -public: - virtual void writeFork(std::ostream& out) const { } - virtual ~Fork() {} -}; - -class Resources : public Fork -{ - std::vector resources; -public: - Resources() {} - Resources(std::istream& in); - void writeFork(std::ostream& out) const; - void addResource(Resource res) { resources.push_back(res); } - - void addResources(const Resources& res); -}; - -void byte(std::ostream& out, int byte) -{ - out.put((unsigned char)byte); -} -void word(std::ostream& out, int word) -{ - byte(out,(word >> 8) & 0xFF); - byte(out,word & 0xFF); -} -void ostype(std::ostream& out, std::string type) -{ - assert(type.size() == 4); - out << type; -} -void longword(std::ostream& out, int longword) -{ - byte(out,(longword >> 24) & 0xFF); - byte(out,(longword >> 16) & 0xFF); - byte(out,(longword >> 8) & 0xFF); - byte(out,longword & 0xFF); -} - -int byte(std::istream& in) -{ - return in.get() & 0xFF; -} -int word(std::istream& in) -{ - int a = byte(in); - int b = byte(in); - return (a << 8) | b; -} -std::string ostype(std::istream& in) -{ - char s[5]; - in.read(s,4); - s[4] = 0; - return s; -} -int longword(std::istream& in) -{ - int a = byte(in); - int b = byte(in); - int c = byte(in); - int d = byte(in); - return (a << 24) | (b << 16) | (c << 8) | d; -} - -void Resources::addResources(const Resources& res) -{ - resources.insert(resources.end(),res.resources.begin(), res.resources.end()); -} - -void Resources::writeFork(std::ostream& out) const -{ - std::streampos start = out.tellp(); - longword(out,0x100); - longword(out,0); - longword(out,0); - longword(out,0); - out.seekp(start + std::streampos(0x100)); - std::map< std::string, std::map > resourceInfos; - std::streampos datastart = out.tellp(); - for(std::vector::const_iterator p = resources.begin(); p != resources.end(); ++p) - { - const std::string& data = p->getData(); - resourceInfos[ p->getType() ][ p->getID() ] = out.tellp() - datastart; - longword(out, data.size()); - out << data; - } - std::streampos dataend = out.tellp(); -// while(out.tellp() % 0x100) -// out.put(0); - std::streampos resmap = out.tellp(); - out.seekp(16+4+2+2, std::ios::cur); - word(out,16+4+2+2+2+2); // offset to resource type list - std::streampos resnameOffset = out.tellp(); - word(out,0); - std::streampos typelist = out.tellp(); - word(out,resourceInfos.size() - 1); - for(std::map< std::string, std::map >::iterator p = resourceInfos.begin(); - p != resourceInfos.end(); ++p) - { - if(p->second.size()) - { - ostype(out,p->first); - word(out,p->second.size()-1); - word(out,0); // replaced later - } - } - int typeIndex = 0; - for(std::map< std::string, std::map >::iterator p = resourceInfos.begin(); - p != resourceInfos.end(); ++p) - { - if(p->second.size()) - { - std::streampos pos = out.tellp(); - out.seekp((int)typelist + 2 + 8 * typeIndex + 6); - word(out, pos - typelist); - out.seekp(pos); - typeIndex++; - - for(std::map::iterator q = p->second.begin(); q != p->second.end(); ++q) - { - word(out,q->first); - word(out,-1); - longword(out,q->second); - longword(out,0); - } - } - } - std::streampos resnames = out.tellp(); - out.seekp(resnameOffset); - word(out, resnames - resmap); - out.seekp(resnames); - std::streampos end = out.tellp(); - out.seekp(start + std::streampos(4)); - longword(out, resmap - start); - longword(out, dataend - start - std::streampos(0x100)); - longword(out, end - resmap); - out.seekp(end); -} - -Resources::Resources(std::istream &in) -{ - std::streampos start = in.tellg(); - int resdataOffset = longword(in); - int resmapOffset = longword(in); - - in.seekg(start + std::streampos(resmapOffset + 16 + 4 + 2 + 2)); - int typeListOffset = word(in); - int nameListOffset = word(in); - int nTypes = (word(in) + 1) & 0xFFFF; - - for(int i = 0; i < nTypes; i++) - { - in.seekg(start + std::streampos(resmapOffset + typeListOffset + 2 + i * 8)); - std::string type = ostype(in); - int nRes = (word(in) + 1) & 0xFFFF; - int refListOffset = word(in); - - for(int j = 0; j < nRes; j++) - { - in.seekg(start + std::streampos(resmapOffset + typeListOffset + refListOffset + j * 12)); - int id = word(in); - int nameOffset = word(in); - int attr = byte(in); - int off1 = byte(in); - int off2 = byte(in); - int off3 = byte(in); - int offset = (off1 << 16) | (off2 << 8) | off3; - std::string name; - if(nameOffset != 0xFFFF) - { - in.seekg(start + std::streampos(resmapOffset + nameListOffset + nameOffset)); - int nameLen = byte(in); - char buf[256]; - in.read(buf, nameLen); - name = std::string(buf, nameLen); - } - - in.seekg(start + std::streampos(resdataOffset + offset)); - int size = longword(in); - std::vector tmp(size); - in.read(tmp.data(), size); - std::string data(tmp.data(), size); - - addResource(Resource(type, id, data, name, attr)); - } - } -} // CRC 16 table lookup array static unsigned short CRC16Table[256] = @@ -446,7 +242,7 @@ int main(int argc, char *argv[]) std::string fn = argv[i++]; std::string flt = readfile(fn); - rsrc.addResource(Resource("CODE", 0, + rsrc.addResource(Resource(ResType("CODE"), 0, fromhex( "00000028 00000000 00000008 00000020" "0000 3F3C 0001 A9F0" diff --git a/Rez/CMakeLists.txt b/Rez/CMakeLists.txt new file mode 100644 index 0000000000..0c7a08f08a --- /dev/null +++ b/Rez/CMakeLists.txt @@ -0,0 +1,71 @@ +# Copyright 2012 Wolfgang Thaller. +# +# This file is part of Retro68. +# +# Retro68 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. +# +# Retro68 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 Retro68. If not, see . + +cmake_minimum_required(VERSION 2.8) + +set(CMAKE_CXX_FLAGS "--std=c++11 -Wall") + +find_package(Boost COMPONENTS wave filesystem system thread regex program_options) + +if(Boost_FOUND) + +find_package(BISON REQUIRED) + +include_directories(. ${CMAKE_CURRENT_BINARY_DIR}) + +add_custom_command( + DEPENDS RezParser.yy + COMMAND ${BISON_EXECUTABLE} + ARGS -o ${CMAKE_CURRENT_BINARY_DIR}/RezParser.generated.cc + ${CMAKE_CURRENT_SOURCE_DIR}/RezParser.yy --graph + COMMENT "Generating parser.cpp" + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/RezParser.generated.cc ${CMAKE_CURRENT_BINARY_DIR}/RezParser.generated.hh +) +add_library(RezLib + RezParser.yy RezParser.generated.hh RezParser.generated.cc + + RezLexer.h + RezLexer.cc + RezLexerWaveToken.h + RezLexerNextToken.cc + + RezWorld.cc + RezWorld.h + + ResourceDefinitions.cc + ResourceDefinitions.h + + Expression.cc + Expression.h + + ResourceCompiler.cc + ResourceCompiler.h + + ResSpec.h + ) +target_link_libraries(RezLib ResourceFiles ${Boost_LIBRARIES}) + +add_executable(Rez + Rez.cc + ) +target_link_libraries(Rez RezLib ResourceFiles ${Boost_LIBRARIES}) + +install(TARGETS Rez RUNTIME DESTINATION bin) + +add_subdirectory(Test) + +endif(Boost_FOUND) diff --git a/Rez/Expression.cc b/Rez/Expression.cc new file mode 100644 index 0000000000..6994541c39 --- /dev/null +++ b/Rez/Expression.cc @@ -0,0 +1,211 @@ +#include "Expression.h" +#include "ResourceCompiler.h" +#include +#include +#include + +int Expression::evaluateInt(ResourceCompiler *ctx) +{ + throw TypeError(); +} + +std::string Expression::evaluateString(ResourceCompiler *ctx) +{ + throw TypeError(); +} + +Expression::~Expression() +{ +} + + +StringExpr::~StringExpr() +{ +} + +std::string StringExpr::evaluateString(ResourceCompiler *ctx) +{ + return str; +} + + +IntExpr::~IntExpr() +{ +} + +int IntExpr::evaluateInt(ResourceCompiler *ctx) +{ + return val; +} + + +void CompoundExpr::addItem(ExprPtr item) +{ + items.push_back(item); +} + +CompoundExpr::~CompoundExpr() +{ +} + + +BinaryExpr::~BinaryExpr() +{ +} + +int BinaryExpr::evaluateInt(ResourceCompiler *ctx) +{ + switch(op) + { + case BinaryOp::XOR: + return a->evaluateInt(ctx) ^ b->evaluateInt(ctx); + case BinaryOp::OR: + return a->evaluateInt(ctx) | b->evaluateInt(ctx); + case BinaryOp::AND: + return a->evaluateInt(ctx) & b->evaluateInt(ctx); + case BinaryOp::SHIFTLEFT: + return a->evaluateInt(ctx) << b->evaluateInt(ctx); + case BinaryOp::SHIFTRIGHT: + return a->evaluateInt(ctx) >> b->evaluateInt(ctx); + case BinaryOp::EQUAL: + return a->evaluateInt(ctx) == b->evaluateInt(ctx); + case BinaryOp::NOTEQUAL: + return a->evaluateInt(ctx) != b->evaluateInt(ctx); + case BinaryOp::PLUS: + return a->evaluateInt(ctx) + b->evaluateInt(ctx); + case BinaryOp::MINUS: + return a->evaluateInt(ctx) - b->evaluateInt(ctx); + case BinaryOp::MULTIPLY: + return a->evaluateInt(ctx) * b->evaluateInt(ctx); + case BinaryOp::DIVIDE: + return a->evaluateInt(ctx) / b->evaluateInt(ctx); + default: + throw TypeError(); + break; + } +} + +std::string BinaryExpr::evaluateString(ResourceCompiler *ctx) +{ + switch(op) + { + case BinaryOp::CONCAT: + return a->evaluateString(ctx) + b->evaluateString(ctx); + default: + throw TypeError(); + break; + } +} + + +UnaryExpr::~UnaryExpr() +{ +} + +int UnaryExpr::evaluateInt(ResourceCompiler *ctx) +{ + switch(op) + { + case UnaryOp::MINUS: + return -a->evaluateInt(ctx); + case UnaryOp::COMPLEMENT: + return ~a->evaluateInt(ctx); + } +} + + +IdentifierExpr::IdentifierExpr(std::string id) + : id(id) +{ +} + +void IdentifierExpr::addArgument(ExprPtr e) +{ + arguments.push_back(e); +} + +ExprPtr IdentifierExpr::lookup(ResourceCompiler *ctx) +{ + Subscripts sub; + for(auto arg : arguments) + sub.addSubscript(arg->evaluateInt(ctx)); + ExprPtr val = ctx->lookupIdentifier(id, sub); + assert(val); + return val; +} + +int IdentifierExpr::evaluateInt(ResourceCompiler *ctx) +{ + if(ctx->isPrePass()) + return 0; + return lookup(ctx)->evaluateInt(ctx); +} + +std::string IdentifierExpr::evaluateString(ResourceCompiler *ctx) +{ + return lookup(ctx)->evaluateString(ctx); +} + + +CaseExpr::CaseExpr(const std::string &tag, CompoundExprPtr expr) + : tag(tag), expr(expr) +{ +} + + +int CountOfExpr::evaluateInt(ResourceCompiler *ctx) +{ + assert(arg->arguments.size() == 0); + return ctx->getArrayCount(arg->id); +} + + +int ArrayIndexExpr::evaluateInt(ResourceCompiler *ctx) +{ + assert(arg->arguments.size() == 0); + return ctx->getArrayIndex(arg->id); +} + + +std::string ReadExpr::evaluateString(ResourceCompiler *ctx) +{ + std::string filename = arg->evaluateString(ctx); + std::ifstream instream(filename); + // ### TODO: check error + return std::string(std::istreambuf_iterator(instream.rdbuf()), + std::istreambuf_iterator()); +} + + +int UnimplementedExpr::evaluateInt(ResourceCompiler *ctx) +{ + std::cerr << msg << std::endl; + return 0; +} + +std::string UnimplementedExpr::evaluateString(ResourceCompiler *ctx) +{ + std::cerr << msg << std::endl; + return ""; +} + + +PeekExpr::PeekExpr(ExprPtr addr, ExprPtr offset, ExprPtr size) + : addr(addr), offset(offset), size(size) +{ +} + +PeekExpr::PeekExpr(ExprPtr addr, int size) + : addr(addr), + offset(std::make_shared(0)), + size(std::make_shared(size)) +{ +} + +int PeekExpr::evaluateInt(ResourceCompiler *ctx) +{ + int p = addr->evaluateInt(ctx) + offset->evaluateInt(ctx); + int s = size->evaluateInt(ctx); + + return ctx->peek(p, s); +} diff --git a/Rez/Expression.h b/Rez/Expression.h new file mode 100644 index 0000000000..0300ec6aec --- /dev/null +++ b/Rez/Expression.h @@ -0,0 +1,162 @@ +#ifndef EXPRESSION_H +#define EXPRESSION_H + +#include +#include + +class ResourceCompiler; + +class Expression; +class CompoundExpr; +class IdentifierExpr; +class CaseExpr; +typedef std::shared_ptr ExprPtr; +typedef std::shared_ptr CompoundExprPtr; +typedef std::shared_ptr IdentifierExprPtr; +typedef std::shared_ptr CaseExprPtr; + + +enum class BinaryOp +{ + XOR, OR, AND, SHIFTLEFT, SHIFTRIGHT, EQUAL, NOTEQUAL, PLUS, MINUS, MULTIPLY, DIVIDE, CONCAT +}; + +enum class UnaryOp +{ + MINUS, COMPLEMENT +}; + +class TypeError +{ +}; + +class Expression +{ +public: + virtual int evaluateInt(ResourceCompiler *ctx); + virtual std::string evaluateString(ResourceCompiler *ctx); + virtual ~Expression(); +}; + +class StringExpr : public Expression +{ + std::string str; +public: + StringExpr(const std::string& str) : str(str) {} + ~StringExpr(); + virtual std::string evaluateString(ResourceCompiler *ctx); +}; + +class IntExpr : public Expression +{ + int val; +public: + IntExpr(int val) : val(val) {} + ~IntExpr(); + + virtual int evaluateInt(ResourceCompiler *ctx); +}; + +class CompoundExpr : public Expression +{ + std::vector items; +public: + void addItem(ExprPtr item); + ExprPtr getItem(int i) const { return items[i]; } + int size() const { return items.size(); } + + ~CompoundExpr(); +}; + +class CaseExpr : public Expression +{ + std::string tag; + CompoundExprPtr expr; + friend class SwitchField; +public: + CaseExpr(const std::string& tag, CompoundExprPtr expr); +}; + +class BinaryExpr : public Expression +{ + BinaryOp op; + ExprPtr a, b; +public: + BinaryExpr(BinaryOp op, ExprPtr a, ExprPtr b) + : op(op), a(a), b(b) {} + ~BinaryExpr(); + + virtual int evaluateInt(ResourceCompiler *ctx); + virtual std::string evaluateString(ResourceCompiler *ctx); +}; + +class UnaryExpr : public Expression +{ + UnaryOp op; + ExprPtr a; +public: + UnaryExpr(UnaryOp op, ExprPtr a) + : op(op), a(a) {} + ~UnaryExpr(); + + virtual int evaluateInt(ResourceCompiler *ctx); +}; + +class IdentifierExpr : public Expression +{ +public: + std::string id; + std::vector arguments; + IdentifierExpr(std::string id); + + void addArgument(ExprPtr e); + ExprPtr lookup(ResourceCompiler *ctx); + virtual int evaluateInt(ResourceCompiler *ctx); + virtual std::string evaluateString(ResourceCompiler *ctx); +}; + +class CountOfExpr : public Expression +{ + IdentifierExprPtr arg; +public: + CountOfExpr(IdentifierExprPtr arg) : arg(arg) {} + virtual int evaluateInt(ResourceCompiler *ctx); +}; + +class ArrayIndexExpr : public Expression +{ + IdentifierExprPtr arg; +public: + ArrayIndexExpr(IdentifierExprPtr arg) : arg(arg) {} + virtual int evaluateInt(ResourceCompiler *ctx); +}; + +class ReadExpr : public Expression +{ + ExprPtr arg; +public: + ReadExpr(ExprPtr arg) : arg(arg) {} + virtual std::string evaluateString(ResourceCompiler *ctx); +}; + +class UnimplementedExpr : public Expression +{ + std::string msg; +public: + UnimplementedExpr(std::string msg) : msg(msg) {} + virtual int evaluateInt(ResourceCompiler *ctx); + virtual std::string evaluateString(ResourceCompiler *ctx); +}; + +class PeekExpr : public Expression +{ + ExprPtr addr; + ExprPtr offset; + ExprPtr size; +public: + PeekExpr(ExprPtr addr, ExprPtr offset, ExprPtr size); + PeekExpr(ExprPtr addr, int size); + virtual int evaluateInt(ResourceCompiler *ctx); +}; + +#endif // EXPRESSION_H diff --git a/Rez/README.md b/Rez/README.md new file mode 100644 index 0000000000..807dc0cffd --- /dev/null +++ b/Rez/README.md @@ -0,0 +1,12 @@ +Rez Resource Compiler +===================== + +A reimplementation of the classic Rez resource compiler. + + +Known Bugs & Limitations +----------------- + +* `$ABCD` alternate syntax for hex numbers is not supported +* hex strings not supported +* Other than in Apple Rez, the preprocessor is case sensitive. diff --git a/Rez/ResSpec.h b/Rez/ResSpec.h new file mode 100644 index 0000000000..2aa648a061 --- /dev/null +++ b/Rez/ResSpec.h @@ -0,0 +1,24 @@ +#ifndef REZSPEC_H +#define REZSPEC_H + +#include "ResType.h" +#include + +class ResSpec : public ResRef +{ + int attr_; + std::string name_; + +public: + ResSpec() {} + ResSpec(ResType type, int id, int attr = 0, std::string name = "") + : ResRef(type, id), attr_(attr), name_(name) + {} + + int& attr() { return attr_; } + int attr() const { return attr_; } + std::string& name() { return name_; } + const std::string& name() const { return name_; } +}; + +#endif // REZSPEC_H diff --git a/Rez/ResourceCompiler.cc b/Rez/ResourceCompiler.cc new file mode 100644 index 0000000000..f332d91d4e --- /dev/null +++ b/Rez/ResourceCompiler.cc @@ -0,0 +1,191 @@ +#include "ResourceCompiler.h" +#include +#include "ResourceDefinitions.h" + +ResourceCompiler::ResourceCompiler( + TypeDefinitionPtr type, CompoundExprPtr body, bool verboseFlag) + : typeDefinition(type), + body(body), + currentField(nullptr) +{ + this->verboseFlag = verboseFlag; +} + +BinaryOutput::BinaryOutput() + : verboseFlag(false) +{ + reset(true); +} + +void BinaryOutput::reset(bool prePass) +{ + currentOffset = 0; + if(!prePass) + prePassData = std::move(data); + data.clear(); + this->prePass = prePass; +} + +std::string BinaryOutput::resourceData() +{ + return std::string(data.begin(), data.end()); +} + +void BinaryOutput::write(int nBits, int value) +{ + if(verboseFlag) + std::cout << "[" << nBits << " bits] = " << std::hex << value << std::dec << std::endl; + + unsigned mask = 1 << (nBits-1); + + for(int i = 0; i < nBits; i++) + { + bool bit = (value & mask) != 0; + + if(currentOffset % 8 == 0) + data.push_back(bit ? 0x80 : 0); + else if(bit) + data.back() |= (0x80 >> (currentOffset % 8)); + ++currentOffset; + + mask >>= 1; + } + + //currentOffset += nBits; +} + +int BinaryOutput::peek(int bitPos, int size) +{ + unsigned bytePos = bitPos / 8; + unsigned endBytePos = (bitPos + size - 1) / 8 + 1; + + unsigned bitPosInByte = bitPos % 8; + unsigned outPos = 32 - size; + + unsigned val = 0; + + for(unsigned i = bytePos; i != endBytePos; ++i) + { + unsigned byte; + if(i < data.size()) + byte = data[i]; + else if(i < prePassData.size()) + byte = prePassData[i]; + else + byte = 0; + + unsigned read = byte << (bitPosInByte + 24); + val |= (read >> outPos); + + outPos += 8 - bitPosInByte; + + bitPosInByte = 0; + } + + return val; +} + +ExprPtr ResourceCompiler::lookupIdentifier(std::string name, const Subscripts &sub) +{ + if(currentField) + { + if(ExprPtr val = currentField->lookupNamedValue(name)) + { + return val; + } + } + + auto p = labelValues.find(std::make_pair(name, sub)); + if(p != labelValues.end()) + return p->second; + + std::cerr << "ID lookup failed: " << name << std::endl; + + return nullptr; +} + +void ResourceCompiler::defineLabel(const std::string &name) +{ + labelValues[std::make_pair(name,currentSubscripts)] = std::make_shared(currentOffset); +} + +void ResourceCompiler::compile() +{ + if(verboseFlag) std::cout << "(first pass)\n"; + reset(true); + typeDefinition->compile(body, this, true); + if(verboseFlag) std::cout << "(second pass)\n"; + + reset(false); + typeDefinition->compile(body, this, false); + if(verboseFlag) std::cout << "(done)\n"; +} + +int ResourceCompiler::getArrayCount(const std::string &name) +{ + Subscripts sub = currentSubscripts; + for(;;) + { + auto p = arrayCounts.find(std::make_pair(name, sub)); + if(p != arrayCounts.end()) + return p->second; + + + if(sub.empty()) + return 0; /* ### */ + sub.popSubscript(); + } +} + +int ResourceCompiler::getArrayIndex(const std::string &arrayName) +{ + return curArrayIndices[arrayName]; +} + +void ResourceCompiler::beginArrayScope(std::string &arrayName, int index) +{ + if(arrayName != "") + { + curArrayIndices[arrayName] = index; + int& count = arrayCounts[std::make_pair(arrayName, currentSubscripts)]; + if(count < index) + count = index; + arrayCounts[std::make_pair(arrayName, Subscripts())] = count; + //std::cout << "count for " << arrayName << " is " << count << std::endl; + } + currentSubscripts.addSubscript(index); +} + +Subscripts::Subscripts() +{ +} + +Subscripts::~Subscripts() +{ +} + +void Subscripts::addSubscript(int x) +{ + subscripts.push_back(x); +} + +void Subscripts::popSubscript() +{ + subscripts.pop_back(); +} + +bool Subscripts::operator<(const Subscripts &other) const +{ + if(subscripts.size() < other.subscripts.size()) + return true; + if(other.subscripts.size() < subscripts.size()) + return false; + for(int i = 0, n = subscripts.size(); i < n; i++) + { + if(subscripts[i] < other.subscripts[i]) + return true; + else if(subscripts[i] > other.subscripts[i]) + return false; + } + return false; +} diff --git a/Rez/ResourceCompiler.h b/Rez/ResourceCompiler.h new file mode 100644 index 0000000000..6b11777dac --- /dev/null +++ b/Rez/ResourceCompiler.h @@ -0,0 +1,94 @@ +#ifndef RESOURCECOMPILER_H +#define RESOURCECOMPILER_H + +#include "Expression.h" +#include "ResourceDefinitions.h" + +class Field; + + +class Subscripts +{ + std::vector subscripts; +public: + Subscripts(); + ~Subscripts(); + + void addSubscript(int x); + void popSubscript(); + bool operator<(const Subscripts& other) const; + bool empty() const { return subscripts.empty(); } +}; + +class BinaryOutput +{ +protected: + int currentOffset; + std::vector data; + std::vector prePassData; + bool verboseFlag; + bool prePass; +public: + BinaryOutput(); + void reset(bool prePass); + + std::string resourceData(); + + void reserve(int nBits) { write(nBits, 0); } + void write(int nBits, int value); + int tell() { return currentOffset; } + + int peek(int bitPos, int size); + + bool isPrePass() { return prePass; } +}; + +class ResourceCompiler : public BinaryOutput +{ + TypeDefinitionPtr typeDefinition; + CompoundExprPtr body; + std::map, ExprPtr> labelValues; + std::map, int> arrayCounts; + std::map curArrayIndices; + Field* currentField; + Subscripts currentSubscripts; + + + + void beginArrayScope(std::string& arrayName, int index); + +public: + ResourceCompiler(TypeDefinitionPtr type, CompoundExprPtr body, bool verboseFlag); + + + ExprPtr lookupIdentifier(std::string name, const Subscripts& sub = Subscripts()); + + void defineLabel(const std::string& name); + void compile(); + + int getArrayCount(const std::string& arrayName); + int getArrayIndex(const std::string& arrayName); + + + class FieldScope + { + ResourceCompiler *compiler; + public: + FieldScope(ResourceCompiler* compiler, Field *field) + : compiler(compiler) { compiler->currentField = field; } + ~FieldScope() { compiler->currentField = nullptr; } + }; + + class ArrayScope + { + ResourceCompiler *compiler; + public: + ArrayScope(ResourceCompiler* compiler, std::string& arrayName, int index) + : compiler(compiler) { compiler->beginArrayScope(arrayName, index); } + ~ArrayScope() { compiler->currentSubscripts.popSubscript(); } + }; +}; + + + +#endif // RESOURCECOMPILER_H diff --git a/Rez/ResourceDefinitions.cc b/Rez/ResourceDefinitions.cc new file mode 100644 index 0000000000..50d663abcb --- /dev/null +++ b/Rez/ResourceDefinitions.cc @@ -0,0 +1,327 @@ +#include "ResourceDefinitions.h" +#include +#include + +#include "ResourceCompiler.h" + + + +std::ostream &operator<<(std::ostream &out, TypeSpec ts) +{ + out << ts.getType(); + if(ts.hasID()) + out << " (" << ts.getID() << ")"; + return out; +} + + +FieldList::~FieldList() +{ + +} + +void FieldList::addField(FieldPtr field) +{ + fields.push_back(field); +} + +void FieldList::addLabel(std::string name) +{ + addField(std::make_shared(name)); +} + +void FieldList::compile(ExprPtr expr, ResourceCompiler *compiler, bool prePass) +{ + CompoundExprPtr compound = std::dynamic_pointer_cast(expr); + assert(compound); + + int i = 0; + for(FieldPtr f : fields) + { + if(f->needsValue()) + f->compile(compound->getItem(i++), compiler, prePass); + else + f->compile(nullptr, compiler, prePass); + } +} + + + + +void SimpleField::addNamedValue(std::string n) +{ + if(lastNamedValue) + addNamedValue(n, std::make_shared( + BinaryOp::PLUS, lastNamedValue, std::make_shared(1))); + else + addNamedValue(n, std::make_shared(0)); +} + +void SimpleField::addNamedValue(std::string n, ExprPtr val) +{ + namedValues[n] = val; + lastNamedValue = val; +} + +ExprPtr SimpleField::lookupNamedValue(std::string n) +{ + auto p = namedValues.find(n); + if(p != namedValues.end()) + return p->second; + else + return nullptr; +} + +bool SimpleField::needsValue() +{ + return !value; +} + +void SimpleField::compile(ExprPtr expr, ResourceCompiler *compiler, bool prePass) +{ + switch(type) + { + case Type::bitstring: + case Type::boolean: + case Type::byte: + case Type::integer: + case Type::longint: + compileInt(expr, compiler, prePass); + break; + case Type::string: + case Type::wstring: + case Type::pstring: + case Type::char_: + compileString(expr, compiler, prePass); + break; + + case Type::rect: + case Type::point: + compileCompound(expr, compiler, prePass); + break; + + } +} + +void SimpleField::compileString(ExprPtr expr, ResourceCompiler *compiler, bool prePass) +{ + std::string str; + { + ResourceCompiler::FieldScope scope(compiler, this); + str = (value ? value : expr)->evaluateString(compiler); + } + + if(arrayCount || type == Type::char_) + { + unsigned requestedSize = type == Type::char_ ? 1 : arrayCount->evaluateInt(compiler); + if(requestedSize < str.size()) + str.erase(str.begin() + requestedSize, str.end()); + else if(requestedSize > str.size()) + str.insert(str.end(),requestedSize - str.size(), '\0'); + } + + int count = str.size(); + + if(type == Type::pstring) + { + if(count > 255) + { + str.erase(str.begin() + 255, str.end()); + count = 255; + } + compiler->write(8, count); + } + else if(type == Type::wstring) + { + if(count > 65535) + { + str.erase(str.begin() + 65535, str.end()); + count = 65535; + } + compiler->write(16, count); + } + + for(char c : str) + compiler->write(8, c); +} + +void SimpleField::compileInt(ExprPtr expr, ResourceCompiler *compiler, bool prePass) +{ + int bitSize = 0; + + switch(type) + { + case Type::bitstring: + bitSize = arrayCount->evaluateInt(compiler); + break; + case Type::boolean: + bitSize = 1; + break; + case Type::byte: + bitSize = 8; + break; + case Type::integer: + bitSize = 16; + break; + case Type::longint: + bitSize = 32; + break; + default: + assert(false); + } + + int actualValue = 0; + ResourceCompiler::FieldScope scope(compiler, this); + actualValue = (value ? value : expr)->evaluateInt(compiler); + + compiler->write(bitSize, actualValue); +} + +void SimpleField::compileCompound(ExprPtr expr, ResourceCompiler *compiler, bool prePass) +{ + ExprPtr val = value ? value : expr; + if(IdentifierExprPtr id = std::dynamic_pointer_cast(val)) + { + ResourceCompiler::FieldScope scope(compiler, this); + val = id->lookup(compiler); + } + + int count = 0; + switch(type) + { + case Type::rect: + count = 4; + break; + case Type::point: + count = 2; + break; + default: + assert(false); + } + + CompoundExprPtr compound = std::dynamic_pointer_cast(val); + assert(compound); + + assert(compound->size() == count); + + for(int i = 0; i < count; i++) + { + int x = compound->getItem(i)->evaluateInt(compiler); + compiler->write(16, x); + } +} + + +ArrayField::ArrayField(std::string name, ExprPtr count) + : name(name), arrayCount(count) +{ +} + +void ArrayField::compile(ExprPtr expr, ResourceCompiler *compiler, bool prePass) +{ + CompoundExprPtr compound = std::dynamic_pointer_cast(expr); + assert(compound); + + + int i = 0; + int n = compound->size(); + + int iterations = 0; + while(i < n) + { + ++iterations; + ResourceCompiler::ArrayScope scope(compiler, name, iterations); + for(FieldPtr f : fields) + { + if(f->needsValue()) + { + assert(i < n); + f->compile(compound->getItem(i++), compiler, prePass); + } + else + f->compile(nullptr, compiler, prePass); + } + } + + if(!prePass && arrayCount) + { + int expected = arrayCount->evaluateInt(compiler); + assert(expected == iterations); + } +} + + +LabelField::LabelField(std::string name) + : name(name) +{ +} + +bool LabelField::needsValue() +{ + return false; +} + +void LabelField::compile(ExprPtr expr, ResourceCompiler *compiler, bool prePass) +{ + compiler->defineLabel(name); +} + + +void SwitchField::addCase(const std::string name, FieldListPtr alternative) +{ + cases[name] = alternative; +} + +void SwitchField::compile(ExprPtr expr, ResourceCompiler *compiler, bool prePass) +{ + CaseExprPtr caseExpr = std::dynamic_pointer_cast(expr); + assert(caseExpr); + + FieldListPtr caseDefinition = cases[caseExpr->tag]; + assert(caseDefinition); + + caseDefinition->compile(caseExpr->expr, compiler, prePass); +} + + +FillAlignField::FillAlignField(FillAlignField::Type type, bool isAlign, ExprPtr count) + : type(type), count(count), isAlign(isAlign) +{ + +} + +bool FillAlignField::needsValue() +{ + return false; +} + +void FillAlignField::compile(ExprPtr expr, ResourceCompiler *compiler, bool prePass) +{ + int bitSize; + switch(type) + { + case Type::bit: bitSize = 1; break; + case Type::nibble: bitSize = 4; break; + case Type::byte: bitSize = 8; break; + case Type::word: bitSize = 16; break; + case Type::long_: bitSize = 32; break; + } + + int actualCount = 1; + if(count) + actualCount = count->evaluateInt(compiler); + + for(int i = 0; i < actualCount; i++) + { + int n; + if(isAlign) + { + int mask = bitSize - 1; + int pos = compiler->tell(); + n = ((pos + mask) & ~mask) - pos; + } + else + n = bitSize; + compiler->write(n, 0); + } +} diff --git a/Rez/ResourceDefinitions.h b/Rez/ResourceDefinitions.h new file mode 100644 index 0000000000..e2d5bb828f --- /dev/null +++ b/Rez/ResourceDefinitions.h @@ -0,0 +1,166 @@ +#ifndef RESOURCEDEFINITIONS_H +#define RESOURCEDEFINITIONS_H + +#include +#include +#include + +#include "Expression.h" +#include "ResType.h" + + +class TypeSpec +{ + ResType type; + int id; +public: + static const int noID = 65536; + + TypeSpec() : id(noID) {} + TypeSpec(ResType type) : type(type), id(noID) {} + TypeSpec(ResType type, int id) : type(type), id(id) {} + + ResType getType() const { return type; } + int getID() const { return id; } + + bool hasID() const { return id != noID; } + + bool operator<(TypeSpec y) const + { + if(type < y.type) + return true; + else if(y.type < type) + return false; + else + return id < y.id; + } +}; + +std::ostream& operator<<(std::ostream& out, TypeSpec ts); + + +class ResourceCompiler; + +class Field +{ +public: + virtual bool needsValue() { return true; } + + virtual void compile(ExprPtr expr, ResourceCompiler *compiler, bool prePass) = 0; + + virtual ExprPtr lookupNamedValue(std::string) { return nullptr; } +}; +typedef std::shared_ptr FieldPtr; + +class SimpleField : public Field +{ +public: + enum class Type + { + boolean, byte, integer, longint, rect, point, char_, + pstring, wstring, string, bitstring + }; + + enum class Attrs + { + none = 0, hex = 1, key = 2, unsigned_ = 4, literal = 8 + }; + + Type type; + Attrs attrs = Attrs::none; + ExprPtr arrayCount; + + ExprPtr value; + std::map namedValues; + ExprPtr lastNamedValue; + + void addNamedValue(std::string n); + void addNamedValue(std::string n, ExprPtr val); + ExprPtr lookupNamedValue(std::string); + + virtual bool needsValue(); + virtual void compile(ExprPtr expr, ResourceCompiler *compiler, bool prePass); + +private: + void compileString(ExprPtr expr, ResourceCompiler *compiler, bool prePass); + void compileInt(ExprPtr expr, ResourceCompiler *compiler, bool prePass); + void compileCompound(ExprPtr expr, ResourceCompiler *compiler, bool prePass); +}; +typedef std::shared_ptr SimpleFieldPtr; + +class FillAlignField : public Field +{ +public: + enum class Type + { + bit, nibble, byte, word, long_ + }; + + FillAlignField(Type type, bool isAlign, ExprPtr count = ExprPtr()); + virtual bool needsValue(); + virtual void compile(ExprPtr expr, ResourceCompiler *compiler, bool prePass); +private: + Type type; + ExprPtr count; + bool isAlign; +}; + +inline SimpleField::Attrs operator|(SimpleField::Attrs a, SimpleField::Attrs b) +{ + return SimpleField::Attrs( int(a) | int(b) ); +} + + +class LabelField : public Field +{ + std::string name; +public: + LabelField(std::string name); + + virtual bool needsValue(); + virtual void compile(ExprPtr expr, ResourceCompiler *compiler, bool prePass); +}; +typedef std::shared_ptr LabelFieldPtr; + + +class FieldList : public Field +{ +protected: + std::vector fields; +public: + virtual ~FieldList(); + void addField(FieldPtr field); + void addLabel(std::string name); + + virtual void compile(ExprPtr expr, ResourceCompiler *compiler, bool prePass); +}; +typedef std::shared_ptr FieldListPtr; + + +class ArrayField : public FieldList +{ + std::string name; + ExprPtr arrayCount; +public: + ArrayField(std::string name /* or empty */, ExprPtr count /* may be null*/); + + virtual void compile(ExprPtr expr, ResourceCompiler *compiler, bool prePass); +}; +typedef std::shared_ptr ArrayFieldPtr; + +class SwitchField : public Field +{ + std::map cases; +public: + void addCase(const std::string name, FieldListPtr alternative); + + virtual void compile(ExprPtr expr, ResourceCompiler *compiler, bool prePass); +}; +typedef std::shared_ptr SwitchFieldPtr; + +class TypeDefinition : public FieldList +{ +}; +typedef std::shared_ptr TypeDefinitionPtr; + +#endif // RESOURCEDEFINITIONS_H diff --git a/Rez/Rez.cc b/Rez/Rez.cc new file mode 100644 index 0000000000..d902e95ca0 --- /dev/null +++ b/Rez/Rez.cc @@ -0,0 +1,113 @@ +#include +#include "boost/program_options.hpp" +#include "boost/filesystem.hpp" +#include "boost/filesystem/fstream.hpp" + +#include "RezParser.generated.hh" +#include "RezLexer.h" +#include "RezWorld.h" + +#include "ResourceFiles.h" +#include "BinaryIO.h" + +namespace po = boost::program_options; +namespace fs = boost::filesystem; + +static po::options_description desc; + +static void usage() +{ + std::cerr << "Usage: " << "Rez [options] input-file\n"; + std::cerr << desc << std::endl; +} + +int main(int argc, const char *argv[]) +{ + desc.add_options() + ("help,h", "show this help message") + ("output,o", po::value()->default_value("rez.output.rsrc"), "output file") + ("append,a", "append to existing output file") + ("type,t", po::value()->default_value("rsrc"), "output file finder type code") + ("creator,c", po::value()->default_value("RSED"), "output file finder creator code") + ("define,D", po::value>(), "predefine preprocessor symbol") + ("include,I", po::value>(), "add include file path") + ("debug,d", "debug logging") + ; + po::options_description hidden, alldesc; + hidden.add_options() + ("input", po::value>(), "input file" ) + ; + alldesc.add(desc).add(hidden); + + po::variables_map options; + try + { + auto parsed = po::command_line_parser(argc, argv) + .options(alldesc) + .positional(po::positional_options_description().add("input", -1)) + .style(po::command_line_style::default_style | + po::command_line_style::allow_long_disguise) + .run(); + + po::store(parsed, options); + } + catch(po::error& e) + { + std::cerr << "ERROR: " << e.what() << std::endl << std::endl; + usage(); + return 1; + } + + po::notify(options); + + if(options.count("help") || !options.count("input")) + { + usage(); + return 0; + } + + RezWorld world; + + if(options.count("debug")) + world.verboseFlag = true; + + std::string outfile = options["output"].as(); + fs::path dataPath = outfile; + fs::create_directory(dataPath.parent_path() / ".rsrc"); + fs::create_directory(dataPath.parent_path() / ".finf"); + fs::path rsrcPath = dataPath.parent_path() / ".rsrc" / dataPath.filename(); + fs::path finfPath = dataPath.parent_path() / ".finf" / dataPath.filename(); + + if(options.count("append")) + { + fs::ifstream rsrcIn(rsrcPath); + world.getResources().addResources(Resources(rsrcIn)); + } + + for(std::string fn : options["input"].as>()) + { + RezLexer lexer(fn); + + for(std::string define : options["define"].as>()) + lexer.addDefine(define); + for(std::string path : options["include"].as>()) + lexer.addIncludePath(path); + + + RezParser parser(lexer, world); + parser.parse(); + } + + { + fs::ofstream dataOut(dataPath); + fs::ofstream rsrcOut(rsrcPath); + fs::ofstream finfOut(finfPath); + + world.getResources().writeFork(rsrcOut); + ostype(finfOut, options["type"].as()); + ostype(finfOut, options["creator"].as()); + for(int i = 8; i < 32; i++) + byte(finfOut, 0); + } + return 0; +} diff --git a/Rez/RezLexer.cc b/Rez/RezLexer.cc new file mode 100644 index 0000000000..fa0703bb80 --- /dev/null +++ b/Rez/RezLexer.cc @@ -0,0 +1,144 @@ +#include "RezLexer.h" + +#include +#include +#include +#include + +#include "RezLexerWaveToken.h" + +namespace wave = boost::wave; + +using namespace boost::wave; + +static std::string readContents(std::istream&& instream) +{ + instream.unsetf(std::ios::skipws); + + return std::string(std::istreambuf_iterator(instream.rdbuf()), + std::istreambuf_iterator()); +} + +static std::string preFilter(std::string str) +{ + boost::regex endif("#endif[^\r\n]*"); + str = boost::regex_replace(str, endif, "#endif"); + + boost::regex dollar_escape("\\\\\\$([a-zA-Z0-9][a-zA-Z0-9])"); + str = boost::regex_replace(str, dollar_escape, "\\\\0x$1"); + + return str; +} + +struct load_file_to_string_filtered +{ + template + class inner + { + public: + template + static void init_iterators(IterContextT &iter_ctx, + PositionT const &act_pos, language_support language) + { + typedef typename IterContextT::iterator_type iterator_type; + + // read in the file + std::ifstream instream(iter_ctx.filename.c_str()); + if (!instream.is_open()) { + BOOST_WAVE_THROW_CTX(iter_ctx.ctx, preprocess_exception, + bad_include_file, iter_ctx.filename.c_str(), act_pos); + return; + } + + iter_ctx.instring = preFilter(readContents(std::move(instream))); + + iter_ctx.first = iterator_type( + iter_ctx.instring.begin(), iter_ctx.instring.end(), + PositionT(iter_ctx.filename), language); + iter_ctx.last = iterator_type(); + } + + private: + std::string instring; + }; +}; + + + +typedef wave::cpplexer::lex_iterator< + wave::cpplexer::lex_token<> > + lex_iterator_type; +typedef wave::context< + std::string::iterator, lex_iterator_type, + load_file_to_string_filtered> + context_type; +typedef context_type::iterator_type pp_iterator_type; + +struct RezLexer::Priv +{ + std::string input; + context_type ctx; + pp_iterator_type iter; + + Priv(std::string data, std::string name) + : input(data), ctx(input.begin(), input.end(), name.c_str()) + { + } +}; + +RezLexer::RezLexer(std::string filename) + : RezLexer(filename, readContents(std::ifstream(filename))) +{ +} + +RezLexer::RezLexer(std::string filename, const std::string &data) +{ + pImpl.reset(new Priv(preFilter(data), filename)); + + pImpl->ctx.add_include_path("/home/wolfgang/Projects/Retro68/RIncludes"); + pImpl->ctx.add_macro_definition("DeRez=0"); + pImpl->ctx.add_macro_definition("Rez=1"); + pImpl->ctx.add_macro_definition("true=1"); + pImpl->ctx.add_macro_definition("false=0"); + + pImpl->iter = pImpl->ctx.begin(); +} + +RezLexer::~RezLexer() +{ + +} + + + +void RezLexer::addDefine(std::string str) +{ + pImpl->ctx.add_macro_definition(str); +} + +void RezLexer::addIncludePath(std::string path) +{ + pImpl->ctx.add_include_path(path.c_str()); +} + +bool RezLexer::atEnd() +{ + return pImpl->iter == pImpl->ctx.end(); +} + +RezLexer::WaveToken RezLexer::nextWave() +{ + if(pImpl->iter == pImpl->ctx.end()) + return WaveToken(); + else + { + WaveToken tok = *pImpl->iter++; + return tok; + } +} + +RezLexer::WaveToken RezLexer::peekWave() +{ + return pImpl->iter == pImpl->ctx.end() ? WaveToken() : *pImpl->iter; +} + diff --git a/Rez/RezLexer.h b/Rez/RezLexer.h new file mode 100644 index 0000000000..4bc4891374 --- /dev/null +++ b/Rez/RezLexer.h @@ -0,0 +1,32 @@ +#ifndef REZLEXER_H +#define REZLEXER_H + +#include + +class RezSymbol; + +class RezLexer +{ + struct Priv; + std::unique_ptr pImpl; + + std::string curFile; + + class WaveToken; + + bool atEnd(); + WaveToken nextWave(); + WaveToken peekWave(); + +public: + RezLexer(std::string filename); + RezLexer(std::string filename, const std::string& data); + ~RezLexer(); + + RezSymbol nextToken(); + + void addDefine(std::string str); + void addIncludePath(std::string path); +}; + +#endif // REZLEXER_H diff --git a/Rez/RezLexerNextToken.cc b/Rez/RezLexerNextToken.cc new file mode 100644 index 0000000000..b00d3e2b4f --- /dev/null +++ b/Rez/RezLexerNextToken.cc @@ -0,0 +1,290 @@ +#include "RezLexer.h" +#include "RezLexerWaveToken.h" +#include "RezParser.generated.hh" +#include + +#include + +using namespace boost::wave; + +static int readInt(const char *str, const char *end = NULL, int baseOverride = 0) +{ + int x = 0; + + int base = 10; + + if(baseOverride) + base = baseOverride; + else if(*str == '0') + { + base = 8; + ++str; + if(*str == 'x' || *str == 'X') + { + base = 16; + ++str; + } + if(*str == 'b' || *str == 'B') + { + base = 2; + ++str; + } + } + else if(*str == 'b' || *str == 'B') + { + base = 2; + ++str; + } + + while(str != end && *str) + { + x *= base; + if(*str >= 'a' && *str <= 'z') + x += *str - 'a' + 10; + else if(*str >= 'A' && *str <= 'Z') + x += *str - 'A' + 10; + else if(*str >= '0' && *str <= '9') + x += *str - '0'; + str++; + } + + return x; +} + +static int readCharLit(const char *str) +{ + const char *p = str + 1; + const char *e = str + strlen(str) - 1; + + if(e - p != 4) + std::cout << "warning: CHAR LITERAL " << str << "\n"; + + int x = 0; + while(p != e) + { + x <<= 8; + x |= (*p) & 0xFF; + ++p; + } + return x; +} + +static std::string readStringLit(const char *str) +{ + const char *p = str + 1; + const char *e = str + strlen(str) - 1; + + std::ostringstream out; + + while(p != e) + { + if(*p == '\\') + { + ++p; + if(p != e) + { + switch(*p) + { + case 'n': + out << '\n'; ++p; + break; + case 'r': + out << '\r'; ++p; + break; + case 't': + out << '\t'; ++p; + break; + case '0': + case '1': + case '2': + case '3': + if(p + 3 > e) + continue; + if(p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) + { + if(p + 4 > e) + continue; + out << (char)readInt(p+2, p+4, 16); + p += 4; + } + else + { + out << (char)readInt(p, p+3, 8); + p += 3; + } + break; + case '$': + { + if(p + 3 > e) + continue; + out << (char)readInt(p+1, p+3, 16); + p += 3; + } + break; + } + } + } + else + { + out << *p++; + } + } + + return out.str(); +} + +RezSymbol RezLexer::nextToken() +{ + for(auto tok = nextWave(); tok != T_EOI && tok != T_EOF; tok = nextWave()) + { + if(IS_CATEGORY(tok, WhiteSpaceTokenType)) + continue; + else if(IS_CATEGORY(tok, EOLTokenType)) + continue; + else if(tok == T_PP_LINE) + { + while(tok != T_EOI && tok != T_EOF && !IS_CATEGORY(tok, EOLTokenType)) + tok = nextWave(); + continue; + } + else + { + //std::cout << "{" << std::hex << (token_id)tok << std::dec << "|" << tok.get_value() << "}\n"; + + auto pos = tok.get_position(); + curFile = pos.get_file().c_str(); + auto yypos = yy::position(&curFile, pos.get_line(), pos.get_column()); + yy::location loc(yypos); + + if(tok == (UnknownTokenType | '"')) + { + return RezParser::make_STRINGLIT("Hello, world.", loc); + } + else if(IS_CATEGORY(tok, IdentifierTokenType) || IS_CATEGORY(tok, KeywordTokenType) || IS_CATEGORY(tok, BoolLiteralTokenType)) + { + typedef decltype(&RezParser::make_TYPE) memfun; +#define KEYWORD(upper, lower) \ +{ lower, &RezParser::make_ ## upper } + + static std::unordered_map keywords = { + KEYWORD(TYPE, "type"), + KEYWORD(RESOURCE, "resource"), + KEYWORD(DATA, "data"), + KEYWORD(READ, "read"), + KEYWORD(INCLUDE, "include"), + KEYWORD(CHANGE, "change"), + KEYWORD(DELETE, "delete"), + + KEYWORD(ARRAY,"array"), + KEYWORD(SWITCH, "switch"), + KEYWORD(CASE, "case"), + KEYWORD(AS, "as"), + KEYWORD(FILL,"fill"), + KEYWORD(ALIGN, "align"), + KEYWORD(HEX,"hex"), + KEYWORD(KEY, "key"), + KEYWORD(WIDE,"wide"), + KEYWORD(UNSIGNED, "unsigned"), + KEYWORD(LITERAL, "literal"), + KEYWORD(BOOLEAN, "boolean"), + KEYWORD(BIT, "bit"), + KEYWORD(NIBBLE, "nibble"), + KEYWORD(BYTE, "byte"), + KEYWORD(CHAR, "char"), + KEYWORD(WORD, "word"), + KEYWORD(INTEGER, "integer"), + KEYWORD(LONG, "long"), + KEYWORD(LONGINT, "longint"), + KEYWORD(PSTRING, "pstring"), + KEYWORD(PSTRING, "wstring"), + KEYWORD(STRING, "string"), + KEYWORD(POINT, "point"), + KEYWORD(RECT, "rect"), + KEYWORD(BITSTRING, "bitstring"), + + KEYWORD(INTEGER, "int"), + KEYWORD(DOLLAR, "$"), + + KEYWORD(FUN_COUNTOF, "$$countof"), + KEYWORD(FUN_ARRAYINDEX, "$$arrayindex"), + KEYWORD(FUN_READ, "$$read"), + KEYWORD(FUN_BITFIELD, "$$bitfield"), + KEYWORD(FUN_WORD, "$$word"), + KEYWORD(FUN_BYTE, "$$byte"), + KEYWORD(FUN_LONG, "$$long"), + }; + + std::string s = tok.get_value().c_str(); + std::string lower = s; + std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); + auto p = keywords.find(lower); + if(p == keywords.end()) + { + //std::cout << "id: " << s << std::endl; + return RezParser::make_IDENTIFIER(lower, loc); + } + else + { + //std::cout << "key: " << s << std::endl; + return (*p->second)(loc); + } + } + else if(tok == T_INTLIT) + { + if(tok.get_value() == "0") + { + auto tok2 = peekWave(); + while(tok2 != T_EOI && tok2 != T_EOF && IS_CATEGORY(tok2, WhiteSpaceTokenType)) + nextWave(), tok2 = peekWave(); + + //std::cout << "!" << std::hex << (token_id)tok2 << std::dec << "|" << tok2.get_value() << "!\n"; + static boost::regex binlit("[bB][01]+"); + if(tok2 == T_IDENTIFIER && boost::regex_match(tok2.get_value().c_str(), binlit)) + tok = nextWave(); + } + return RezParser::make_INTLIT(readInt(tok.get_value().c_str()), loc); + } + else + { +#define NOVAL_TOK(name) \ +case T_ ## name: /*std::cout << #name << std::endl;*/ return RezParser::make_ ## name(loc) + switch(token_id(tok)) + { + case T_INTLIT: return RezParser::make_INTLIT(readInt(tok.get_value().c_str()), loc); + + case T_CHARLIT: return RezParser::make_CHARLIT(readCharLit(tok.get_value().c_str()), loc); + case T_STRINGLIT: return RezParser::make_STRINGLIT(readStringLit(tok.get_value().c_str()), loc); + + NOVAL_TOK(LEFTBRACE); + NOVAL_TOK(RIGHTBRACE); + NOVAL_TOK(LEFTBRACKET); + NOVAL_TOK(RIGHTBRACKET); + NOVAL_TOK(LEFTPAREN); + NOVAL_TOK(RIGHTPAREN); + NOVAL_TOK(SEMICOLON); + NOVAL_TOK(COMMA); + NOVAL_TOK(PLUS); + NOVAL_TOK(MINUS); + NOVAL_TOK(DIVIDE); + NOVAL_TOK(STAR); + NOVAL_TOK(ASSIGN); + NOVAL_TOK(COLON); + NOVAL_TOK(SHIFTLEFT); + NOVAL_TOK(SHIFTRIGHT); + NOVAL_TOK(EQUAL); + NOVAL_TOK(NOTEQUAL); + NOVAL_TOK(AND); + NOVAL_TOK(OR); + NOVAL_TOK(XOR); + NOVAL_TOK(COMPL); + + default: + + return RezParser::make_BADTOKEN(tok.get_value().c_str(), loc); + } + + } + } + } + return RezSymbol(); +} diff --git a/Rez/RezLexerWaveToken.h b/Rez/RezLexerWaveToken.h new file mode 100644 index 0000000000..3d11903b0b --- /dev/null +++ b/Rez/RezLexerWaveToken.h @@ -0,0 +1,15 @@ +#ifndef REZLEXERWAVETOKEN_H +#define REZLEXERWAVETOKEN_H + +#include "RezLexer.h" + +#include + +class RezLexer::WaveToken : public boost::wave::cpplexer::lex_token<> +{ +public: + WaveToken() = default; + WaveToken(const boost::wave::cpplexer::lex_token<> & o) : boost::wave::cpplexer::lex_token<>(o) {} +}; + +#endif // REZLEXERWAVETOKEN_H diff --git a/Rez/RezParser.yy b/Rez/RezParser.yy new file mode 100644 index 0000000000..0b6e9d49af --- /dev/null +++ b/Rez/RezParser.yy @@ -0,0 +1,462 @@ +%require "3.0.2" +%defines +%define parser_class_name {RezParser} +%skeleton "lalr1.cc" + +%locations; + +%define api.token.constructor +%define api.value.type variant +%define parse.assert + +%token IDENTIFIER; +%token CHARLIT; +%token STRINGLIT; +%token INTLIT; + +%token BADTOKEN; + + +%token LEFTBRACE "{"; +%token RIGHTBRACE "}"; +%token LEFTBRACKET "["; +%token RIGHTBRACKET "]"; +%token LEFTPAREN "("; +%token RIGHTPAREN ")"; +%token SEMICOLON ";"; +%token COMMA ","; +%token PLUS "+"; +%token MINUS "-"; +%token DIVIDE "/"; +%token STAR "*"; +%token ASSIGN "="; +%token COLON ":"; +%token SHIFTLEFT "<<"; +%token SHIFTRIGHT ">>"; +%token EQUAL "=="; +%token NOTEQUAL "!="; +%token AND "&"; +%token OR "|"; +%token XOR "^"; +%token COMPL "~"; +%token DOLLAR "$"; + +%token TYPE "type"; +%token RESOURCE "resource"; +%token DATA "data"; +%token READ "read"; +%token INCLUDE "include"; +%token CHANGE "change"; +%token DELETE "delete"; + + +%token ARRAY "array"; +%token SWITCH "switch"; +%token CASE "case"; +%token AS "as"; +%token FILL "fill"; +%token ALIGN "align"; +%token HEX "hex"; +%token KEY "key"; +%token WIDE "wide"; +%token LITERAL "literal"; +%token UNSIGNED "unsigned"; + +%token BOOLEAN "boolean"; +%token BIT "bit"; +%token NIBBLE "nibble"; +%token BYTE "byte"; +%token CHAR "char"; +%token WORD "word"; +%token INTEGER "integer"; +%token LONG "long"; +%token LONGINT "longint"; +%token PSTRING "pstring"; +%token WSTRING "wstring"; +%token STRING "string"; +%token POINT "point"; +%token RECT "rect"; +%token BITSTRING "bitstring"; + +%token FUN_COUNTOF "$$countof"; +%token FUN_ARRAYINDEX "$$arrayindex"; +%token FUN_READ "$$read"; +%token FUN_BITFIELD "$$bitfield"; +%token FUN_WORD "$$word"; +%token FUN_BYTE "$$byte"; +%token FUN_LONG "$$long"; + +/* +%left "|"; +%left "^"; +%left "&"; +%left "==" "!="; +%left ">>" "<<"; +%left "+" "-"; +%left "*" "/"; +*/ + +%param { RezLexer& lexer } +%param { RezWorld& world } + +%code requires { + #include "ResourceDefinitions.h" + #include "Expression.h" + #include "ResSpec.h" + + #define YY_NULLPTR nullptr + class RezLexer; + class RezWorld; +} + +%code provides { + using yy::RezParser; + //using RezSymbol = yy::RezParser::symbol_type; + + class RezSymbol : public yy::RezParser::symbol_type + { + public: + RezSymbol() = default; + RezSymbol(const yy::RezParser::symbol_type& x) : yy::RezParser::symbol_type(x) {} + }; +} + +%code { + #include "RezLexer.h" + #include "RezWorld.h" + #include "ResourceCompiler.h" + + static yy::RezParser::symbol_type yylex(RezLexer& lexer, RezWorld&) + { + return lexer.nextToken(); + } + + void yy::RezParser::error(const location_type& loc, std::string const& err) + { + std::cerr << loc << ": " << err << std::endl; + } + + static std::string fromHex(std::string hex) + { + std::string bin; + int nibble; + bool haveNibble = false; + for(std::string::iterator p = hex.begin(); p != hex.end(); ++p) + { + if(std::isspace(*p)) + continue; + assert(isdigit(*p) || (tolower(*p) >= 'a' && tolower(*p) <= 'f')); + int digit; + if(isdigit(*p)) + digit = *p - '0'; + else + digit = tolower(*p) - 'a' + 0xA; + + if(haveNibble) + { + bin += (char) ((nibble << 4) | digit); + haveNibble = false; + } + else + { + nibble = digit; + haveNibble = true; + } + } + return bin; + } + +} + +%% +%start rez; + +rez : %empty + | rez type_definition ";" + | rez resource ";" + | rez data ";" + ; + +type_definition : "type" type_spec + { + TypeDefinitionPtr td = std::make_shared(); + world.addTypeDefinition($type_spec, td); + world.fieldLists.push(td); + } + "{" field_definitions "}" + { world.fieldLists.pop(); if(world.verboseFlag) std::cout << "TYPE " << $2 << std::endl; } + | "type" type_spec "as" type_spec + { if(world.verboseFlag) std::cout << "TYPE " << $2 << std::endl; } + ; + +%type res_type; +res_type : CHARLIT { $$ = ResType($1); } ; + +%type type_spec; +type_spec : res_type { $$ = TypeSpec($res_type); } + | res_type "(" INTLIT ")" { $$ = TypeSpec($res_type, $INTLIT); } + ; + +field_definitions : %empty + | field_definitions IDENTIFIER ":" { world.fieldLists.top()->addLabel($2); } + | field_definitions ";" + | field_definitions field_definition ";" { world.fieldLists.top()->addField($2); } + ; + +%type field_definition; +field_definition: simple_field_definition { $$ = $1; } + | array_definition { $$ = $1; } + | switch_definition { $$ = $1; } + | fill_statement { $$ = $1; } + | align_statement { $$ = $1; } + ; + +%type simple_field_definition; +simple_field_definition: field_attributes simpletype array_count_opt value_spec_opt + { + $$ = std::make_shared(); + $$->attrs = $field_attributes; + $$->type = $simpletype; + $$->arrayCount = $array_count_opt; + $$->value = $value_spec_opt; + } + | simple_field_definition IDENTIFIER + { $$ = $1; $$->addNamedValue($IDENTIFIER); } + | simple_field_definition IDENTIFIER "=" value + { $$ = $1; $$->addNamedValue($IDENTIFIER, $value); } + | simple_field_definition "," IDENTIFIER + { $$ = $1; $$->addNamedValue($IDENTIFIER); } + | simple_field_definition "," IDENTIFIER "=" value + { $$ = $1; $$->addNamedValue($IDENTIFIER, $value); } + ; + +%type array_count array_count_opt value_spec_opt value resource_item; +%type expression expression1 expression2 ; +%type expression3 expression4 expression5 ; +%type expression6 expression7 expression8; + +value_spec_opt : %empty { $$ = nullptr; } | "=" value { $$ = $2; } ; + +%type simpletype; +simpletype : "boolean" { $$ = SimpleField::Type::boolean; } + | "byte" { $$ = SimpleField::Type::byte; } + | "integer" { $$ = SimpleField::Type::integer; } + | "longint" { $$ = SimpleField::Type::longint; } + | "rect" { $$ = SimpleField::Type::rect; } + | "point" { $$ = SimpleField::Type::point; } + | "char" { $$ = SimpleField::Type::char_; } + | "pstring" { $$ = SimpleField::Type::pstring; } + | "wstring" { $$ = SimpleField::Type::wstring; } + | "string" { $$ = SimpleField::Type::string; } + | "bitstring" { $$ = SimpleField::Type::bitstring; } + ; + +%type fill_statement align_statement; +fill_statement : "fill" fill_unit array_count_opt + { $$ = std::make_shared($fill_unit, false, $array_count_opt); } + ; +align_statement : "align" fill_unit + { $$ = std::make_shared($fill_unit, true); } + ; + +%type fill_unit; +fill_unit : "bit" { $$ = FillAlignField::Type::bit; } + | "nibble" { $$ = FillAlignField::Type::nibble; } + | "byte" { $$ = FillAlignField::Type::byte; } + | "word" { $$ = FillAlignField::Type::word; } + | "long" { $$ = FillAlignField::Type::long_; } + ; + +%type array_definition; +array_definition: + array_attributes "array" array_name_opt array_count_opt + { + ArrayFieldPtr af = std::make_shared($array_name_opt, $array_count_opt); + world.fieldLists.push(af); + } + "{" field_definitions "}" + { + $$ = world.fieldLists.top(); + world.fieldLists.pop(); + } + ; + +array_count : "[" expression "]" { $$ = $2; } +array_count_opt : %empty { $$ = nullptr; } | array_count { $$ = $1; }; + +%type array_name_opt; +array_name_opt : %empty { $$ = ""; } | IDENTIFIER { $$ = $1; } ; + +array_attributes: %empty | "wide" ; + +%type field_attributes field_attribute; +field_attributes: %empty { $$ = SimpleField::Attrs::none; } + | field_attributes field_attribute { $$ = $1 | $2; } + ; + +field_attribute : "hex" { $$ = SimpleField::Attrs::hex; } + | "key" { $$ = SimpleField::Attrs::key; } + | "unsigned" { $$ = SimpleField::Attrs::unsigned_; } + | "literal" { $$ = SimpleField::Attrs::literal; } + ; + +%type switch_definition; +switch_definition: + "switch" + { world.switches.push(std::make_shared()); } + "{" + switch_cases + "}" + { + $$ = world.switches.top(); + world.switches.pop(); + } + ; + +switch_cases : %empty | switch_cases switch_case ; + +switch_case : "case" IDENTIFIER ":" + { + world.fieldLists.push(std::make_shared()); + } + field_definitions + { + world.switches.top()->addCase($IDENTIFIER, world.fieldLists.top()); + world.fieldLists.pop(); + } + ; + + +value : expression { $$ = $1; } + | "{" resource_body "}" { $$ = $2; } + | string_expression { $$ = $1; } + ; + +expression : expression1 { $$ = $1; } + | expression "^" expression1 { $$ = std::make_shared(BinaryOp::XOR, $1, $3); } + ; + +expression1 : expression2 { $$ = $1; } + | expression1 "&" expression2 { $$ = std::make_shared(BinaryOp::AND, $1, $3); } + ; + +expression2 : expression3 { $$ = $1; } + | expression2 "|" expression3 { $$ = std::make_shared(BinaryOp::OR, $1, $3); } + ; + +expression3 : expression4 { $$ = $1; } + | expression3 "==" expression4 { $$ = std::make_shared(BinaryOp::EQUAL, $1, $3); } + | expression3 "!=" expression4 { $$ = std::make_shared(BinaryOp::NOTEQUAL, $1, $3); } + ; + +expression4 : expression5 { $$ = $1; } + | expression4 ">>" expression5 { $$ = std::make_shared(BinaryOp::SHIFTRIGHT, $1, $3); } + | expression4 "<<" expression5 { $$ = std::make_shared(BinaryOp::SHIFTLEFT, $1, $3); } + ; + +expression5 : expression6 { $$ = $1; } + | expression5 "+" expression6 { $$ = std::make_shared(BinaryOp::PLUS, $1, $3); } + | expression5 "-" expression6 { $$ = std::make_shared(BinaryOp::MINUS, $1, $3); } + ; + +expression6 : expression7 { $$ = $1; } + | expression6 "*" expression7 { $$ = std::make_shared(BinaryOp::MULTIPLY, $1, $3); } + | expression6 "/" expression7 { $$ = std::make_shared(BinaryOp::DIVIDE, $1, $3); } + ; +expression7 : expression8 { $$ = $1; } + | "-" expression7 { $$ = std::make_shared(UnaryOp::MINUS, $2); } + | "+" expression7 { $$ = $2; } + | "~" expression7 { $$ = std::make_shared(UnaryOp::COMPLEMENT, $2); } + ; + +expression8 : INTLIT { $$ = std::make_shared($1); } + | CHARLIT { $$ = std::make_shared($1); } + + | identifier_expression { $$ = $1; } + | "(" expression ")" { $$ = $2; } + + | "$$countof" "(" identifier_expression ")" + { $$ = std::make_shared($identifier_expression); } + | "$$arrayindex" "(" identifier_expression ")" + { $$ = std::make_shared($identifier_expression); } + | "$$bitfield" "(" expression "," expression "," expression ")" + { $$ = std::make_shared($3, $5, $7); } + | "$$word" "(" expression ")" + { $$ = std::make_shared($3, 16); } + | "$$byte" "(" expression ")" + { $$ = std::make_shared($3, 8); } + | "$$long" "(" expression ")" + { $$ = std::make_shared($3, 32); } + ; + +%type identifier_expression; +identifier_expression : IDENTIFIER { $$ = std::make_shared($1); } + | IDENTIFIER + { world.functionCalls.push(std::make_shared($1)); } + "[" function_argument_list1 "]" + { $$ = world.functionCalls.top(); world.functionCalls.pop(); } + ; + +function_argument_list : %empty | function_argument_list1 ; +function_argument_list1 : expression + { world.functionCalls.top()->addArgument($expression); } + | function_argument_list "," expression + { world.functionCalls.top()->addArgument($expression); } + ; + +%type string_expression string_expression1; +string_expression : string_expression1 { $$ = $1; } + | string_expression string_expression1 + { $$ = std::make_shared(BinaryOp::CONCAT, $1, $2); } + ; + +%type stringlit; +stringlit : STRINGLIT { $$ = $1; } + | DOLLAR STRINGLIT { $$ = fromHex($2); } + ; + +string_expression1 : stringlit { $$ = std::make_shared($1); } + | "$$read" "(" string_expression ")" + { $$ = std::make_shared($string_expression); } + ; + +resource : "resource" res_spec "{" resource_body "}" + { + world.addResource($res_spec, $resource_body); + } + ; + +%type res_spec; + +res_spec : res_type "(" expression resource_attributes ")" + { $$ = $resource_attributes( ResSpec($res_type, $expression->evaluateInt(nullptr)) ); } + +%type > resource_attributes ; +resource_attributes : %empty { $$ = [](ResSpec s){ return s; }; } + | resource_attributes "," IDENTIFIER { $$ = $1; } + | resource_attributes "," string_expression { $$ = $1; } + + ; + +%type resource_body resource_body1; +resource_body : %empty { $$ = std::make_shared(); } + | resource_body1 { $$ = $1; } + ; +resource_body1 : resource_item { $$ = std::make_shared(); $$->addItem($1); } + | resource_body1 "," resource_item { $$ = $1; $$->addItem($3); } + | resource_body1 ";" resource_item { $$ = $1; $$->addItem($3); } + | resource_body1 ";" { $$ = $1; } + ; + +resource_item : value { $$ = $1; } + | IDENTIFIER "{" resource_body "}" { $$ = std::make_shared($IDENTIFIER, $resource_body); } + ; + + +data : "data" res_spec "{" string_expression "}" +{ + world.addData($res_spec, $string_expression->evaluateString(nullptr)); +} +; + +%% diff --git a/Rez/RezWorld.cc b/Rez/RezWorld.cc new file mode 100644 index 0000000000..93d0d5bfb3 --- /dev/null +++ b/Rez/RezWorld.cc @@ -0,0 +1,45 @@ +#include "RezWorld.h" +#include "ResourceCompiler.h" +#include "ResourceFiles.h" + +#include + +RezWorld::RezWorld() + : verboseFlag(false) +{ +} + +void RezWorld::addTypeDefinition(TypeSpec spec, TypeDefinitionPtr type) +{ + types[spec] = type; +} + +TypeDefinitionPtr RezWorld::getTypeDefinition(ResType type, int id) +{ + auto p = types.find(TypeSpec(type, id)); + if(p != types.end()) + return p->second; + p = types.find(TypeSpec(type)); + if(p != types.end()) + return p->second; + + return nullptr; +} + +void RezWorld::addResource(ResSpec spec, CompoundExprPtr body) +{ + if(verboseFlag) + std::cout << "RESOURCE " << spec.type() << "(" << spec.id() << ", " << "\"" << spec.name() << "\"" << spec.attr() << ")" << std::endl; + TypeDefinitionPtr def = getTypeDefinition(spec.type(), spec.id()); + ResourceCompiler compiler(def, body, verboseFlag); + compiler.compile(); + + resources.addResource(Resource(spec.type(), spec.id(), compiler.resourceData(), spec.name(), spec.attr())); +} + +void RezWorld::addData(ResSpec spec, const std::string &data) +{ + if(verboseFlag) + std::cout << "DATA " << spec.type() << "(" << spec.id() << ", " << "\"" << spec.name() << "\"" << spec.attr() << ")" << std::endl; + resources.addResource(Resource(spec.type(), spec.id(), data, spec.name(), spec.attr())); +} diff --git a/Rez/RezWorld.h b/Rez/RezWorld.h new file mode 100644 index 0000000000..5c047f196a --- /dev/null +++ b/Rez/RezWorld.h @@ -0,0 +1,37 @@ +#ifndef REZWORLD_H +#define REZWORLD_H + +#include +#include +#include +#include "ResourceDefinitions.h" +#include "Expression.h" +#include "ResourceFiles.h" +#include "ResSpec.h" + +class RezWorld +{ + friend class RezParser; + + std::map types; + std::stack fieldLists; + std::stack functionCalls; + std::stack switches; + + Resources resources; +public: + RezWorld(); + void addTypeDefinition(TypeSpec spec, TypeDefinitionPtr type); + + TypeDefinitionPtr getTypeDefinition(ResType type, int id); + + void addResource(ResSpec spec, CompoundExprPtr body); + void addData(ResSpec spec, const std::string& data); + + Resources& getResources() { return resources; } + + bool verboseFlag; +}; + + +#endif // REZWORLD_H diff --git a/Rez/Test.r b/Rez/Test.r new file mode 100644 index 0000000000..a0b7903b5a --- /dev/null +++ b/Rez/Test.r @@ -0,0 +1,33 @@ +/*#include "Types.r" + + +*/ + +#include "/home/wolfgang/Projects/Retro68/CExamples/Sample.r" + +type 'TEST' { + integer zero, one, two, answer = 42, missed; + longint; + integer = (after - before) / 8; + integer = $$CountOf(foo); + before: + array foo { + integer = $$ArrayIndex(foo); + integer; + integer; + }; + string; + after: + ; +}; + +resource 'TEST' (128) { + answer, + 0x1234, + { 1, 2; 3, 4; }, + "Hello, " + "world: " + $"Abcd 1234"; +}; + + diff --git a/Rez/Test/CMakeLists.txt b/Rez/Test/CMakeLists.txt new file mode 100644 index 0000000000..b323384653 --- /dev/null +++ b/Rez/Test/CMakeLists.txt @@ -0,0 +1,4 @@ +set(CMAKE_CXX_FLAGS "--std=c++11 -Wall -Wno-multichar") +find_package(Boost COMPONENTS unit_test_framework) +add_executable(RezUnitTests UnitTests.cc) +target_link_libraries(RezUnitTests RezLib ${Boost_LIBRARIES}) diff --git a/Rez/Test/UnitTests.cc b/Rez/Test/UnitTests.cc new file mode 100644 index 0000000000..e7fefd1f27 --- /dev/null +++ b/Rez/Test/UnitTests.cc @@ -0,0 +1,134 @@ +#define BOOST_TEST_MODULE UnitTests +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MAIN +#include + +#include "RezLexer.h" +#include "RezParser.generated.hh" +#include "ResourceCompiler.h" + +#include + +BOOST_AUTO_TEST_SUITE(LexSuite) + +#define CHECKSYM(TOKEN, TYPE, VAL) \ + do { \ + RezSymbol t = lex.nextToken(); \ + BOOST_CHECK_EQUAL(t.token(), TOKEN); \ + if(t.token() == TOKEN) \ + BOOST_CHECK_EQUAL(t.value.as(), VAL); \ + } while(0) +#define CHECKSYM_(TOKEN) \ + do { \ + RezSymbol t = lex.nextToken(); \ + BOOST_CHECK_EQUAL(t.token(), TOKEN); \ + } while(0) + +BOOST_AUTO_TEST_CASE(basicInt) +{ + RezLexer lex("test", "123 0x456 0xaBcd9\n"); + + CHECKSYM(RezParser::token::INTLIT, int, 123); + CHECKSYM(RezParser::token::INTLIT, int, 0x456); + CHECKSYM(RezParser::token::INTLIT, int, 0xabcd9); + CHECKSYM_(0); +} + +BOOST_AUTO_TEST_CASE(alternateHex) +{ + RezLexer lex("test", "$456 $aBcd9\n"); + + CHECKSYM(RezParser::token::INTLIT, int, 0x456); + CHECKSYM(RezParser::token::INTLIT, int, 0xabcd9); + CHECKSYM_(0); +} + +BOOST_AUTO_TEST_CASE(noNewlineAtEOF) +{ + RezLexer lex("test", "123 456"); + CHECKSYM(RezParser::token::INTLIT, int, 123); + CHECKSYM(RezParser::token::INTLIT, int, 456); + CHECKSYM_(0); +} + +BOOST_AUTO_TEST_CASE(strings) +{ + RezLexer lex("test", R"rez( + "Hello, world." + "Foo \n" + "\r Quux" + "\001\002\003" + "\0x42\0x43" + "Blah \$5F" + )rez" "\n"); + CHECKSYM(RezParser::token::STRINGLIT, std::string, "Hello, world."); + CHECKSYM(RezParser::token::STRINGLIT, std::string, "Foo \n"); + CHECKSYM(RezParser::token::STRINGLIT, std::string, "\r Quux"); + CHECKSYM(RezParser::token::STRINGLIT, std::string, "\001\002\003"); + CHECKSYM(RezParser::token::STRINGLIT, std::string, "\x42\x43"); + CHECKSYM(RezParser::token::STRINGLIT, std::string, "Blah \x5F"); + CHECKSYM_(0); +} + +BOOST_AUTO_TEST_SUITE_END() + +BOOST_AUTO_TEST_SUITE(BinarySuite) + +BOOST_AUTO_TEST_CASE(bytes) +{ + BinaryOutput out; + out.write(8, 'a'); + out.write(8, 'b'); + out.write(8, 'c'); + BOOST_CHECK_EQUAL(out.resourceData(), "abc"); +} + +BOOST_AUTO_TEST_CASE(multibyte) +{ + BinaryOutput out; + out.write(32, 'abcd'); + BOOST_CHECK_EQUAL(out.resourceData(), "abcd"); +} + +BOOST_AUTO_TEST_CASE(subbyte) +{ + BinaryOutput out; + out.write(4, 6); + out.write(4, 1); + + out.write(2, 1); + out.write(2, 2); + out.write(4, 2); + + out.write(3, 3); + out.write(2, 0); + out.write(3, 3); + BOOST_CHECK_EQUAL(out.resourceData(), "abc"); +} + + +BOOST_AUTO_TEST_CASE(peek) +{ + BinaryOutput out; + for(char c : "Hello, world.") + if(c != 0) + out.write(8, c); + + BOOST_CHECK_EQUAL(out.resourceData(), "Hello, world."); + + BOOST_CHECK_EQUAL(out.peek(0,8), 'H'); + BOOST_CHECK_EQUAL(out.peek(32,8), 'o'); + + BOOST_CHECK_EQUAL(out.peek(0,32), 'Hell'); + BOOST_CHECK_EQUAL(out.peek(40,32), ', wo'); + + BOOST_CHECK_EQUAL(out.peek(1,8), 'H' * 2); + BOOST_CHECK_EQUAL(out.peek(2,8), 0x21); + + BOOST_CHECK_EQUAL(out.peek(2,30), 'Hell' & 0x3FFFFFFF); + BOOST_CHECK_EQUAL(out.peek(2,32), ('Hell' & 0x3FFFFFFF) << 2 | ('o' >> 6) ); + + BOOST_CHECK_EQUAL(out.peek(4,3), 4); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/retro68.toolchain.cmake b/retro68.toolchain.cmake index 5d1c110b7c..a2478b5ddd 100644 --- a/retro68.toolchain.cmake +++ b/retro68.toolchain.cmake @@ -22,6 +22,7 @@ set( RETRO68_ROOT "" CACHE PATH "path to root of Retro68 Toolchain" ) set( CMAKE_INSTALL_PREFIX "${RETRO68_ROOT}/m68k-unknown-elf/" CACHE PATH "installation prefix" ) set( MAKE_APPL "${RETRO68_ROOT}/bin/MakeAPPL" ) +set( REZ "${RETRO68_ROOT}/bin/Rez" ) set( CMAKE_C_COMPILER "${RETRO68_ROOT}/bin/m68k-unknown-elf-gcc" ) set( CMAKE_CXX_COMPILER "${RETRO68_ROOT}/bin/m68k-unknown-elf-g++" )