diff options
53 files changed, 2507 insertions, 1303 deletions
| diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d19b18..ad9c0ad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -125,6 +125,7 @@ ADD_LIBRARY(ousia_core  	src/core/common/Exceptions  	src/core/common/Function  	src/core/common/Logger +	src/core/common/Location  	src/core/common/Number  	src/core/common/Property  	src/core/common/Rtti @@ -145,10 +146,13 @@ ADD_LIBRARY(ousia_core  	src/core/model/Project  	src/core/model/Typesystem  	src/core/parser/Parser +	src/core/parser/ParserContext +	src/core/parser/ParserScope  	src/core/parser/ParserStack -	src/core/parser/Scope  	src/core/resource/Resource  	src/core/resource/ResourceLocator +	src/core/resource/ResourceManager +	src/core/resource/ResourceUtils  #	src/core/script/ScriptEngine  ) diff --git a/src/core/CodeTokenizer.cpp b/src/core/CodeTokenizer.cpp index e9c1bbf..fbc1150 100644 --- a/src/core/CodeTokenizer.cpp +++ b/src/core/CodeTokenizer.cpp @@ -26,8 +26,10 @@ Token CodeTokenizer::constructToken(const Token &t)  {  	std::string content = buf.str();  	buf.str(std::string()); -	return Token{returnTokenId,        content,     startToken.startColumn, -	             startToken.startLine, t.endColumn, t.endLine}; +	return Token{ +	    returnTokenId, content, +	    SourceLocation{t.location.getSourceId(), startToken.location.getStart(), +	                   t.location.getEnd()}};  }  void CodeTokenizer::buffer(const Token &t) { buf << t.content; } @@ -40,12 +42,6 @@ bool CodeTokenizer::doPrepare(const Token &t, std::deque<Token> &peeked)  		mode = it->second.mode;  	} -	if (t.startLine != t.endLine && mode != CodeTokenMode::LINEBREAK) { -		throw TokenizerException( -		    "We did not expect a multiline token (except linebreaks). Most " -		    "likely you did not add a linebreak token to your tokenizer!"); -	} -  	switch (state) {  		case CodeTokenizerState::NORMAL:  			switch (mode) { @@ -60,9 +56,8 @@ bool CodeTokenizer::doPrepare(const Token &t, std::deque<Token> &peeked)  					break;  				case CodeTokenMode::LINEBREAK:  					if (!ignoreLinebreaks) { -						peeked.push_back({it->second.id, t.content, -						                  t.startColumn, t.startLine, -						                  t.endColumn, t.endLine}); +						peeked.push_back( +						    {it->second.id, t.content, t.location});  					}  					return !ignoreLinebreaks;  				default: @@ -87,18 +82,21 @@ bool CodeTokenizer::doPrepare(const Token &t, std::deque<Token> &peeked)  									peeked.push_back(Token{  									    TOKEN_TEXT,  									    t.content.substr(begin, (int)c - begin), -									    t.startColumn + begin, t.startLine, -									    t.startColumn + (int)c, t.endLine}); +									    SourceLocation{ +									        t.location.getSourceId(), +									        t.location.getStart() + begin, +									        t.location.getStart() + c}});  									begin = -1;  									empty = false;  								}  							}  						}  						if (begin >= 0) { -							peeked.push_back( -							    Token{TOKEN_TEXT, t.content.substr(begin), -							          t.startColumn + begin, t.startLine, -							          t.endColumn, t.endLine}); +							peeked.push_back(Token{ +							    TOKEN_TEXT, t.content.substr(begin), +							    SourceLocation{t.location.getSourceId(), +							                   t.location.getStart() + begin, +							                   t.location.getEnd()}});  							empty = false;  						}  					} else { diff --git a/src/core/Registry.cpp b/src/core/Registry.cpp index 86665a2..4aad7db 100644 --- a/src/core/Registry.cpp +++ b/src/core/Registry.cpp @@ -16,6 +16,8 @@      along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ +#include <core/common/Exceptions.hpp> +#include <core/common/Utils.hpp>  #include <core/parser/Parser.hpp>  #include <core/resource/Resource.hpp>  #include <core/resource/ResourceLocator.hpp> @@ -24,32 +26,92 @@  namespace ousia { -using namespace parser; -  /* Class Registry */ -void Registry::registerParser(parser::Parser &parser) +void Registry::registerParser(const std::set<std::string> &mimetypes, +                              const RttiSet &types, Parser *parser) +{ +	for (const std::string &mimetype : mimetypes) { +		// Make sure no other parser was given for this mimetype +		auto it = parsers.find(mimetype); +		if (it != parsers.end()) { +			throw OusiaException{std::string{"Parser for mimetype "} + +			                     mimetype + +			                     std::string{" already registered."}}; +		} + +		// Store a reference at the parser and a copy of the given RttiSet +		parsers[mimetype] = std::pair<Parser *, RttiSet>{parser, types}; +	} +} + +static const std::pair<Parser *, RttiSet> NullParser{nullptr, RttiSet{}}; + +const std::pair<Parser *, RttiSet> &Registry::getParserForMimetype( +    const std::string &mimetype) const +{ +	const auto it = parsers.find(mimetype); +	if (it != parsers.end()) { +		return it->second; +	} +	return NullParser; +} + +void Registry::registerExtension(const std::string &extension, +                                 const std::string &mimetype)  { -	parsers.push_back(&parser); -	for (const auto &mime : parser.mimetypes()) { -		//TODO: This does not allow for multiple parsers with the same mimetype. -		// Is that how its supposed to be? -		parserMimetypes.insert(std::make_pair(mime, &parser)); +	// Always use extensions in lower case +	std::string ext = Utils::toLower(extension); + +	// Make sure the extension is unique +	auto it = extensions.find(ext); +	if (it != extensions.end()) { +		throw OusiaException{std::string{"Extension "} + extension + +		                     std::string{" already registered."}};  	} + +	// Register the mimetype +	extensions[ext] = mimetype; +} + +void Registry::registerDefaultExtensions() +{ +	registerExtension("oxd", "text/vnd.ousia.oxd"); +	registerExtension("oxm", "text/vnd.ousia.oxm"); +	registerExtension("opd", "text/vnd.ousia.opd"); +	registerExtension("oss", "text/vnd.ousia.oss"); +	registerExtension("js", "application/javascript");  } -Parser *Registry::getParserForMimetype(const std::string &mimetype) const +std::string Registry::getMimetypeForExtension( +    const std::string &extension) const  { -	const auto it = parserMimetypes.find(mimetype); -	if (it != parserMimetypes.end()) { +	// Always use extensions in lower case +	std::string ext = Utils::toLower(extension); + +	// Try to find the extension +	auto it = extensions.find(ext); +	if (it != extensions.end()) {  		return it->second;  	} -	return nullptr; +	return std::string{}; +} + +std::string Registry::getMimetypeForFilename(const std::string &filename) const +{ +	// Fetch the file extension +	std::string ext = Utils::extractFileExtension(filename); +	if (ext.empty()) { +		return std::string{}; +	} + +	// Fetch the mimetype for the extension +	return getMimetypeForExtension(ext);  } -void Registry::registerResourceLocator(ResourceLocator &locator) +void Registry::registerResourceLocator(ResourceLocator *locator)  { -	locators.push_back(&locator); +	locators.push_back(locator);  }  bool Registry::locateResource(Resource &resource, const std::string &path, diff --git a/src/core/Registry.hpp b/src/core/Registry.hpp index 40eede1..4b4cb65 100644 --- a/src/core/Registry.hpp +++ b/src/core/Registry.hpp @@ -16,37 +16,140 @@      along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ +/** + * @file Registry.hpp + * + * Class used for registering plugin classes. The Registry is one of the central + * classes needed for parsing and transforming an Ousía document. + * + * @author Andreas Stöckel (astoecke@techfak.uni-bielefeld.de) + */ +  #ifndef _OUSIA_REGISTRY_HPP_  #define _OUSIA_REGISTRY_HPP_  #include <map> +#include <set>  #include <vector> +#include <core/common/Rtti.hpp>  #include <core/resource/Resource.hpp>  namespace ousia { -// TODO: Add support for ScriptEngine type - -namespace parser { +// Forward declarations  class Parser; -}  class ResourceLocator; +/** + * The Registry class is the central class which is used to store references to + * all available plugins. + */  class Registry {  private: -	std::vector<parser::Parser *> parsers; -	std::map<std::string, parser::Parser *> parserMimetypes; - +	/** +	 * Mapping between parser mimetypes and pairs of parsers and their supported +	 * Rtti types. +	 */ +	std::map<std::string, std::pair<Parser *, RttiSet>> parsers; + +	/** +	 * Map from file extensions to registered mimetypes. +	 */ +	std::map<std::string, std::string> extensions; + +	/** +	 * List containing all registered ResourceLocator instances. +	 */  	std::vector<ResourceLocator *> locators;  public: -	void registerParser(parser::Parser &parser); - -	parser::Parser *getParserForMimetype(const std::string &mimetype) const; - -	void registerResourceLocator(ResourceLocator &locator); - +	/** +	 * Registers a new parser instance for the given set of mimetypes. Throws +	 * an exception if a parser is already registered for one of the mimetypes. +	 * +	 * @param mimetypes is a set of mimetypes for which the Parser should be +	 * registered. +	 * @param types is a set of node the parser is known to return. This +	 * information is needed in order to prevent inclusion of the wrong Node +	 * types +	 * @param parser is the parser instance that is registered for the given +	 * mimetypes. +	 */ +	void registerParser(const std::set<std::string> &mimetypes, +	                    const RttiSet &types, Parser *parser); + +	/** +	 * Returns a pointer pointing at a Parser that was registered for handling +	 * the given mimetype. +	 * +	 * @param mimetype is the mimetype for which a Parser instance should be +	 * looked up. +	 * @return a pair containing a pointer at the parser and the RttiTypes +	 * supported by the parser. The pointer is set to a nullptr if no such +	 * parser could be found. +	 */ +	const std::pair<Parser *, RttiSet> &getParserForMimetype( +	    const std::string &mimetype) const; + +	/** +	 * Registers a file extension with a certain mimetype. Throws an exception +	 * if a mimetype is already registered for this extension. +	 * +	 * @param extension is the file extension for which the mimetype should be +	 * registered. The extension has to be provided without a leading dot. The +	 * extensions are handled case insensitive. +	 * @param mimetype is the mimetype that should be registered for the +	 * extension. +	 */ +	void registerExtension(const std::string &extension, +	                       const std::string &mimetype); + +	/** +	 * Registers mimetypes for some default extensions. +	 */ +	void registerDefaultExtensions(); + +	/** +	 * Returns the mimetype for the given extension. +	 * +	 * @param extension is the file extension for which the mimetype should be +	 * looked up. The extension has to be provided without a leading dot. The +	 * extensions are handled case insensitive. +	 * @return the registered mimetype or an empty string of none was found. +	 */ +	std::string getMimetypeForExtension(const std::string &extension) const; + +	/** +	 * Tries to deduce the mimetype from the given filename. +	 * +	 * @param filename is the filename from which the mimetype should be +	 * deduced. +	 * @return the mimetype or an empty string if no filename could be deduced. +	 */ +	std::string getMimetypeForFilename(const std::string &filename) const; + +	/** +	 * Registers a ResourceLocator instance that should be used for locating +	 * resources. Two registered ResourceLocator should not be capable of +	 * accessing Resources at the same location. If this happens, the resource +	 * locator that was registered first has precedence. +	 * +	 * @param locator is the ResourceLocator instance that should be registered. +	 */ +	void registerResourceLocator(ResourceLocator *locator); + +	/** +	 * Locates a resource using the registered ResourceLocator instances. +	 * +	 * @param resource is the resource descriptor to which the result will be +	 * written. +	 * @param path is the path under which the resource should be looked up. +	 * @param type is the ResourceType. Specifying a resource type may help to +	 * locate the resource. +	 * @param relativeTo is another resource relative to which the resource may +	 * be looked up. +	 */  	bool locateResource(Resource &resource, const std::string &path,  	                    ResourceType type = ResourceType::UNKNOWN,  	                    const Resource &relativeTo = NullResource) const; diff --git a/src/core/Tokenizer.cpp b/src/core/Tokenizer.cpp index 9d25608..ab4735a 100644 --- a/src/core/Tokenizer.cpp +++ b/src/core/Tokenizer.cpp @@ -81,8 +81,7 @@ bool Tokenizer::prepare()  {  	std::stringstream buffer;  	char c; -	int startColumn = input.getColumn(); -	int startLine = input.getLine(); +	SourcePosition start = input.getOffset();  	bool bufEmpty = true;  	while (input.peek(c)) {  		if (root.children.find(c) != root.children.end()) { @@ -124,20 +123,16 @@ bool Tokenizer::prepare()  				if (bufEmpty) {  					// if we did not have text before, construct that token.  					if (doPrepare( -					        Token{match, tBuf.str(), startColumn, startLine, -					              input.getColumn(), input.getLine()}, +					        Token{match, tBuf.str(), input.getLocation(start)},  					        peeked)) {  						return true;  					} else { -						startColumn = input.getColumn(); -						startLine = input.getLine(); +						start = input.getOffset();  						continue;  					}  				} else {  					// otherwise we return the text before the token. -					if (doPrepare(Token{TOKEN_TEXT, buffer.str(), startColumn, -					                    startLine, input.getColumn(), -					                    input.getLine()}, +					if (doPrepare(Token{TOKEN_TEXT, buffer.str(), input.getLocation(start)},  					              peeked)) {  						return true;  					} else{ @@ -146,8 +141,7 @@ bool Tokenizer::prepare()  						//constructed.  						buffer.str(std::string());  						bufEmpty = true; -						startColumn = input.getColumn(); -						startLine = input.getLine(); +						start = input.getOffset();  						continue;  					}   				} @@ -161,8 +155,7 @@ bool Tokenizer::prepare()  		input.consumePeek();  	}  	if (!bufEmpty) { -		return doPrepare(Token{TOKEN_TEXT, buffer.str(), startColumn, startLine, -		                       input.getColumn(), input.getLine()}, +		return doPrepare(Token{TOKEN_TEXT, buffer.str(), input.getLocation(start)},  		                 peeked);  	}  	return false; diff --git a/src/core/Tokenizer.hpp b/src/core/Tokenizer.hpp index 2b03e17..50e458c 100644 --- a/src/core/Tokenizer.hpp +++ b/src/core/Tokenizer.hpp @@ -121,19 +121,12 @@ static const int TOKEN_TEXT = -2;  struct Token {  	int tokenId;  	std::string content; -	int startColumn; -	int startLine; -	int endColumn; -	int endLine; +	SourceLocation location; -	Token(int tokenId, std::string content, int startColumn, int startLine, -	      int endColumn, int endLine) +	Token(int tokenId, std::string content, SourceLocation location)  	    : tokenId(tokenId),  	      content(content), -	      startColumn(startColumn), -	      startLine(startLine), -	      endColumn(endColumn), -	      endLine(endLine) +	      location(location)  	{  	} diff --git a/src/core/common/CharReader.cpp b/src/core/common/CharReader.cpp index 6966b97..6fd3d45 100644 --- a/src/core/common/CharReader.cpp +++ b/src/core/common/CharReader.cpp @@ -376,50 +376,39 @@ bool Buffer::fetch(CursorId cursor, char &c)  	return fetchCharacter(cursor, c, false);  } -/* CharReader::Cursor class */ - -void CharReader::Cursor::assign(std::shared_ptr<Buffer> buffer, -                                CharReader::Cursor &cursor) -{ -	// Copy the cursor position -	buffer->copyCursor(cursor.cursor, this->cursor); - -	// Copy the state -	line = cursor.line; -	column = cursor.column; -} -  /* CharReader class */ -CharReader::CharReader(std::shared_ptr<Buffer> buffer, size_t line, -                       size_t column) +CharReader::CharReader(std::shared_ptr<Buffer> buffer, SourceId sourceId, +                       size_t offs)      : buffer(buffer), -      readCursor(buffer->createCursor(), line, column), -      peekCursor(buffer->createCursor(), line, column), -      coherent(true) +      readCursor(buffer->createCursor()), +      peekCursor(buffer->createCursor()), +      coherent(true), +      sourceId(sourceId), +      offs(offs)  {  } -CharReader::CharReader(const std::string &str, size_t line, size_t column) -    : CharReader(std::shared_ptr<Buffer>{new Buffer{str}}, line, column) +CharReader::CharReader(const std::string &str, SourceId sourceId, size_t offs) +    : CharReader(std::shared_ptr<Buffer>{new Buffer{str}}, sourceId, offs)  {  } -CharReader::CharReader(std::istream &istream, size_t line, size_t column) -    : CharReader(std::shared_ptr<Buffer>{new Buffer{istream}}, line, column) +CharReader::CharReader(std::istream &istream, SourceId sourceId, size_t offs) +    : CharReader(std::shared_ptr<Buffer>{new Buffer{istream}}, sourceId, offs)  {  }  CharReader::~CharReader()  { -	buffer->deleteCursor(readCursor.cursor); -	buffer->deleteCursor(peekCursor.cursor); +	buffer->deleteCursor(readCursor); +	buffer->deleteCursor(peekCursor);  } -bool CharReader::readAtCursor(Cursor &cursor, char &c) +bool CharReader::readAtCursor(Buffer::CursorId &cursor, char &c)  {  	// Return false if we're at the end of the stream -	if (!buffer->read(cursor.cursor, c)) { +	if (!buffer->read(cursor, c)) {  		return false;  	} @@ -431,24 +420,12 @@ bool CharReader::readAtCursor(Cursor &cursor, char &c)  		// Check whether the next character is a continuation of the  		// current character  		char c2; -		if (buffer->read(cursor.cursor, c2)) { +		if (buffer->read(cursor, c2)) {  			if ((c2 != '\n' && c2 != '\r') || c2 == c) { -				buffer->moveCursor(cursor.cursor, -1); +				buffer->moveCursor(cursor, -1);  			}  		}  	} - -	// Count lines and columns -	if (c == '\n') { -		// A linebreak was reached, go to the next line -		cursor.line++; -		cursor.column = 1; -	} else { -		// Ignore UTF-8 continuation bytes -		if (!((c & 0x80) && !(c & 0x40))) { -			cursor.column++; -		} -	}  	return true;  } @@ -456,7 +433,7 @@ bool CharReader::peek(char &c)  {  	// If the reader was coherent, update the peek cursor state  	if (coherent) { -		peekCursor.assign(buffer, readCursor); +		buffer->copyCursor(readCursor, peekCursor);  		coherent = false;  	} @@ -471,12 +448,8 @@ bool CharReader::read(char &c)  	// Set the peek position to the current read position, if reading was not  	// coherent -	if (!coherent) { -		peekCursor.assign(buffer, readCursor); -		coherent = true; -	} else { -		buffer->copyCursor(readCursor.cursor, peekCursor.cursor); -	} +	buffer->copyCursor(readCursor, peekCursor); +	coherent = true;  	// Return the result of the read function  	return res; @@ -485,7 +458,7 @@ bool CharReader::read(char &c)  void CharReader::resetPeek()  {  	if (!coherent) { -		peekCursor.assign(buffer, readCursor); +		buffer->copyCursor(readCursor, peekCursor);  		coherent = true;  	}  } @@ -493,7 +466,7 @@ void CharReader::resetPeek()  void CharReader::consumePeek()  {  	if (!coherent) { -		readCursor.assign(buffer, peekCursor); +		buffer->copyCursor(peekCursor, readCursor);  		coherent = true;  	}  } @@ -513,7 +486,8 @@ bool CharReader::consumeWhitespace()  CharReaderFork CharReader::fork()  { -	return CharReaderFork(buffer, readCursor, peekCursor, coherent); +	return CharReaderFork{buffer,   readCursor, peekCursor, +	                      sourceId, offs,       coherent};  }  size_t CharReader::readRaw(char *buf, size_t size) @@ -528,155 +502,49 @@ size_t CharReader::readRaw(char *buf, size_t size)  	return res;  } -SourceContext CharReader::getContextAt(ssize_t maxSize, -                                       Buffer::CursorId referenceCursor) -{ -	// Clone the given read cursor -	Buffer::CursorId cur = buffer->createCursor(referenceCursor); - -	// Fetch the start position of the search -	ssize_t offs = buffer->offset(cur); -	ssize_t start = offs; -	ssize_t end = offs; -	char c; - -	// Search the beginning of the line with the last non-whitespace character -	bool hadNonWhitespace = false; -	bool foundBegin = false; -	for (ssize_t i = 0; i < maxSize; i++) { -		// Fetch the character at the current position -		if (buffer->fetch(cur, c)) { -			// Abort, at linebreaks if we found a non-linebreak character -			hadNonWhitespace = hadNonWhitespace || !Utils::isWhitespace(c); -			if (hadNonWhitespace && (c == '\n' || c == '\r')) { -				buffer->moveCursor(cur, 1); -				start++; -				foundBegin = true; -				break; -			} -		} -		if (buffer->moveCursor(cur, -1) == 0) { -			foundBegin = true; -			break; -		} else { -			// Update the start position and the hadNonWhitespace flag -			start--; -		} -	} +bool CharReader::atEnd() const { return buffer->atEnd(readCursor); } -	// Search the end of the line -	buffer->moveCursor(cur, offs - start); -	bool foundEnd = false; -	for (ssize_t i = 0; i < maxSize; i++) { -		// Increment the end counter if a character was read, abort if the end -		// of the stream has been reached -		if (buffer->read(cur, c)) { -			end++; -		} else { -			foundEnd = true; -			break; -		} - -		// Abort on linebreak characters -		if (c == '\n' || c == '\r') { -			foundEnd = true; -			break; -		} -	} - -	// Calculate the truncated start and end position and limit the number of -	// characters to the maximum number of characters -	ssize_t tStart = start; -	ssize_t tEnd = end; -	if (tEnd - tStart > maxSize) { -		tStart = std::max(offs - maxSize / 2, tStart); -		tEnd = tStart + maxSize; -	} - -	// Try to go to the calculated start position and fetch the actual start -	// position -	ssize_t aStart = end + buffer->moveCursor(cur, tStart - end); -	if (aStart > tStart) { -		tEnd = tEnd + (aStart - tStart); -		tStart = aStart; -	} - -	// Read one line -	std::stringstream ss; -	size_t relPos = 0; -	for (ssize_t i = tStart; i < tEnd; i++) { -		if (buffer->read(cur, c)) { -			// Break once a linebreak is reached -			if (c == '\n' || c == '\r') { -				break; -			} - -			// Add the current character to the output -			ss << c; - -			// Increment the string-relative offset as long as the original -			// offset is not reached in the for loop -			if (i < offs) { -				relPos++; -			} -		} -	} - -	// Delete the newly created cursor -	buffer->deleteCursor(cur); - -	return SourceContext{ss.str(), relPos, !foundBegin || tStart != start, -	                     !foundEnd || tEnd != end}; +SourceOffset CharReader::getOffset() const +{ +	return buffer->offset(readCursor) + offs;  } -SourceContext CharReader::getContextAtOffs(ssize_t maxSize, size_t offs) +SourcePosition CharReader::getPosition() const  { -	// Create a new cursor and calculate how far it has to be moved to reach -	// the position specified in the location instance -	Buffer::CursorId cur = buffer->createCursor(); -	ssize_t moveOffs = offs - buffer->offset(cur); - -	// Try to move the cursor to the specified position and read the context -	SourceContext res; -	if (buffer->moveCursor(cur, moveOffs) == moveOffs) { -		res = getContextAt(60, cur); -	} - -	// Delete the read cursor -	buffer->deleteCursor(cur); -	return res; +	return getOffset();  } -SourceContext CharReader::getContext(ssize_t maxSize) +SourceLocation CharReader::getLocation() const  { -	return getContextAt(maxSize, readCursor.cursor); +	return SourceLocation{sourceId, getOffset()};  } -SourceContext CharReader::contextCallback(const SourceLocation &location, -                                          void *data) +SourceLocation CharReader::getLocation(SourcePosition start) const  { -	return static_cast<CharReader *>(data)->getContextAtOffs(60, location.offs); +	return SourceLocation{sourceId, start, getOffset()};  } +SourceId CharReader::getSourceId() const { return sourceId; } +  /* Class CharReaderFork */  CharReaderFork::CharReaderFork(std::shared_ptr<Buffer> buffer, -                               CharReader::Cursor &parentReadCursor, -                               CharReader::Cursor &parentPeekCursor, -                               bool coherent) -    : CharReader(buffer, 1, 1), +                               Buffer::CursorId parentReadCursor, +                               Buffer::CursorId parentPeekCursor, +                               SourceId sourceId, size_t offs, bool coherent) +    : CharReader(buffer, sourceId, offs),        parentReadCursor(parentReadCursor),        parentPeekCursor(parentPeekCursor)  { -	readCursor.assign(buffer, parentReadCursor); -	peekCursor.assign(buffer, parentPeekCursor); +	buffer->copyCursor(parentReadCursor, readCursor); +	buffer->copyCursor(parentPeekCursor, peekCursor);  	this->coherent = coherent;  }  void CharReaderFork::commit()  { -	parentReadCursor.assign(buffer, readCursor); -	parentPeekCursor.assign(buffer, peekCursor); +	buffer->copyCursor(readCursor, parentReadCursor); +	buffer->copyCursor(peekCursor, parentPeekCursor);  }  } diff --git a/src/core/common/CharReader.hpp b/src/core/common/CharReader.hpp index 134d9d9..5a4d906 100644 --- a/src/core/common/CharReader.hpp +++ b/src/core/common/CharReader.hpp @@ -355,54 +355,10 @@ class CharReaderFork;  /**   * Used within parsers for convenient access to single characters in an input   * stream or buffer. It allows reading and peeking single characters from a - * buffer. Additionally it counts the current column/row (with correct handling - * for UTF-8) and contains an internal state machine that handles the detection - * of linebreaks and converts these to a single '\n'. + * buffer. Additionally it contains an internal state machine that handles the + * detection of linebreaks and converts these to a single '\n'.   */  class CharReader { -protected: -	/** -	 * Internally used cursor structure for managing the read and the peek -	 * cursor. -	 */ -	struct Cursor { -		/** -		 * Corresponding cursor in the underlying buffer instance. -		 */ -		const Buffer::CursorId cursor; - -		/** -		 * Current line the cursor is in. -		 */ -		int line; - -		/** -		 * Current column the cursor is in. -		 */ -		int column; - -		/** -		 * Constructor of the Cursor class. -		 * -		 * @param cursor is the underlying cursor in the Buffer instance. -		 * @param line is the line at which the cursor is positioned. -		 * @param column is the column at which the cursor is positioned. -		 */ -		Cursor(Buffer::CursorId cursor, int line, int column) -		    : cursor(cursor), line(line), column(column) -		{ -		} - -		/** -		 * Assigns one cursor to another. -		 * -		 * @param buffer is the underlying buffer instance the internal cursor -		 * belongs to. -		 * @param cursor is the cursor from which the state should be copied. -		 */ -		void assign(std::shared_ptr<Buffer> buffer, Cursor &cursor); -	}; -  private:  	/**  	 * Substitutes "\r", "\n\r", "\r\n" with a single "\n". @@ -411,7 +367,7 @@ private:  	 * @param c a reference to the character that should be written.  	 * @return true if another character needs to be read.  	 */ -	bool substituteLinebreaks(Cursor &cursor, char &c); +	bool substituteLinebreaks(Buffer::CursorId &cursor, char &c);  	/**  	 * Reads a single character from the given cursor. @@ -421,29 +377,7 @@ private:  	 * @return true if a character was read, false if the end of the stream has  	 * been reached.  	 */ -	bool readAtCursor(Cursor &cursor, char &c); - -	/** -	 * Returns the line the given cursor currently is in, but at most the -	 * given number of characters in the form of a Context structure. -	 * -	 * @param maxSize is the maximum length of the extracted context -	 * @param referenceCursor is a cursor in the internal buffer pointing at the -	 * location at which the context should be read. -	 */ -	SourceContext getContextAt(ssize_t maxSize, -	                           Buffer::CursorId referenceCursor); - -	/** -	 * Returns the line the at the given byte offset, but at most the -	 * given number of characters in the form of a Context structure. -	 * -	 * @param maxSize is the maximum length of the extracted context -	 * @param offs is the byte offset for which the context should be read. -	 * @return the context at the specified position or an empty (invalid) -	 * context if the context could not be read. -	 */ -	SourceContext getContextAtOffs(ssize_t maxSize, size_t offs); +	bool readAtCursor(Buffer::CursorId &cursor, char &c);  protected:  	/** @@ -454,12 +388,12 @@ protected:  	/**  	 * Cursor used for reading.  	 */ -	Cursor readCursor; +	Buffer::CursorId readCursor;  	/**  	 * Cursor used for peeking.  	 */ -	Cursor peekCursor; +	Buffer::CursorId peekCursor;  	/**  	 * Set to true as long the underlying Buffer cursor is at the same position @@ -469,33 +403,50 @@ protected:  	bool coherent;  	/** +	 * Id of the underlying source file. +	 */ +	SourceId sourceId; + +	/** +	 * Offset to be added to the underlying buffer byte positions. +	 */ +	size_t offs; + +	/**  	 * Protected constructor of the CharReader base class. Creates new read  	 * and peek cursors for the given buffer.  	 *  	 * @param buffer is a reference to the underlying Buffer class responsible  	 * for allowing to read from a single input stream from multiple locations. +	 * @param sourceId is the ID of the underlying source file. +	 * @param offs is the byte offset at which the char reader should start +	 * counting.  	 */ -	CharReader(std::shared_ptr<Buffer> buffer, size_t line, size_t column); +	CharReader(std::shared_ptr<Buffer> buffer, SourceId sourceId, size_t offs);  public:  	/**  	 * Creates a new CharReader instance from a string.  	 *  	 * @param str is a string containing the input data. -	 * @param line is the start line. -	 * @param column is the start column. +	 * @param sourceId is the ID of the underlying source file. +	 * @param offs is the byte offset at which the char reader should start +	 * counting.  	 */ -	CharReader(const std::string &str, size_t line = 1, size_t column = 1); +	CharReader(const std::string &str, SourceId sourceId = InvalidSourceId, +	           size_t offs = 0);  	/**  	 * Creates a new CharReader instance for an input stream.  	 *  	 * @param istream is the input stream from which incomming data should be  	 * read. -	 * @param line is the start line. -	 * @param column is the start column. +	 * @param sourceId is the ID of the underlying source file. +	 * @param offs is the byte offset at which the char reader should start +	 * counting.  	 */ -	CharReader(std::istream &istream, size_t line = 1, size_t column = 1); +	CharReader(std::istream &istream, SourceId sourceId = InvalidSourceId, +	           size_t offs = 0);  	/**  	 * Deletes the used cursors from the underlying buffer instance. @@ -572,56 +523,52 @@ public:  	size_t readRaw(char *buf, size_t size);  	/** -	 * Returns true if there are no more characters as the stream was -	 * closed. +	 * Returns true if there are no more characters as the stream was closed.  	 *  	 * @return true if there is no more data.  	 */ -	bool atEnd() const { return buffer->atEnd(readCursor.cursor); } +	bool atEnd() const;  	/**  	 * Returns the offset of the read cursor in bytes. +	 * +	 * @return the offset of the read cursor in bytes.  	 */ -	size_t getOffset() const { return buffer->offset(readCursor.cursor); } - -	/** -	 * Returns the line number the read cursor currently is at. -	 */ -	int getLine() const { return readCursor.line; } +	SourceOffset getOffset() const;  	/** -	 * Returns the column the read cursor currently is at. +	 * Returns the offset of the read cursor in bytes. +	 * +	 * @return the offset of the read cursor in bytes.  	 */ -	int getColumn() const { return readCursor.column; } +	SourcePosition getPosition() const;  	/** -	 * Returns the current position of the read cursor (line and column). +	 * Returns a SourceLocation object describing the exact position (including +	 * the source file) of the read cursor. +	 * +	 * @return a SourceLocation object at the position of the current read +	 * cursor.  	 */ -	SourceLocation getLocation() const -	{ -		return SourceLocation(getLine(), getColumn(), getOffset()); -	} +	SourceLocation getLocation() const;  	/** -	 * Returns the line the read cursor currently is in, but at most the -	 * given number of characters in the form of a Context structure. +	 * Returns a SourceLocation object starting at the given start position and +	 * ending at the exact position (including the source file) of the read +	 * cursor.  	 * -	 * @param maxSize is the maximum length of the extracted context +	 * @return a SourceLocation object at the position of the current read +	 * cursor.  	 */ -	SourceContext getContext(ssize_t maxSize = 60); +	SourceLocation getLocation(SourcePosition start) const;  	/** -	 * Function that can be used to provide the context for a certain source -	 * location. A pointer to this function can be supplied to a Logger instance -	 * in the pushFile() method. The data should be set to a pointer to the -	 * CharReader instance. +	 * Returns the current SourceId which describes the Resource on which the +	 * CharReader is currently working.  	 * -	 * @param location is the location for which the context should be returned. -	 * Only the "offs" field within the location is used. -	 * @param data is a pointer pointing at a CharReader instance. +	 * @return the current SourceId.  	 */ -	static SourceContext contextCallback(const SourceLocation &location, -	                                     void *data); +	SourceId getSourceId() const;  };  /** @@ -637,12 +584,12 @@ private:  	/**  	 * The reader cursor of the underlying CharReader instance.  	 */ -	CharReader::Cursor &parentReadCursor; +	Buffer::CursorId parentReadCursor;  	/**  	 * The peek cursor of the underlying CharReader instance.  	 */ -	CharReader::Cursor &parentPeekCursor; +	Buffer::CursorId parentPeekCursor;  	/**  	 * Constructor of the CharReaderFork class. @@ -650,12 +597,14 @@ private:  	 * @param buffer is a reference at the parent Buffer instance.  	 * @param parentPeekCursor is a reference at the parent read cursor.  	 * @param parentPeekCursor is a reference at the parent peek cursor. +	 * @param location is the current location.  	 * @param coherent specifies whether the char reader cursors are initialized  	 * coherently.  	 */  	CharReaderFork(std::shared_ptr<Buffer> buffer, -	               CharReader::Cursor &parentReadCursor, -	               CharReader::Cursor &parentPeekCursor, bool coherent); +	               Buffer::CursorId parentReadCursor, +	               Buffer::CursorId parentPeekCursor, SourceId sourceId, +	               size_t offs, bool coherent);  public:  	/** diff --git a/src/core/common/Exceptions.cpp b/src/core/common/Exceptions.cpp index e368b5a..caba7cc 100644 --- a/src/core/common/Exceptions.cpp +++ b/src/core/common/Exceptions.cpp @@ -22,21 +22,5 @@  namespace ousia { -/* Class LoggableException */ - -std::string LoggableException::formatMessage(const std::string &msg, -                                             const SourceLocation &loc) -{ -	std::stringstream ss; -	ss << "error "; -	if (loc.hasLine()) { -		ss << "at line " << loc.line << ", "; -		if (loc.hasColumn()) { -			ss << "column " << loc.column << " "; -		} -	} -	ss << "with message: " << msg; -	return ss.str(); -}  } diff --git a/src/core/common/Exceptions.hpp b/src/core/common/Exceptions.hpp index 2a88427..0be33b3 100644 --- a/src/core/common/Exceptions.hpp +++ b/src/core/common/Exceptions.hpp @@ -77,14 +77,6 @@ public:   * makes it simple to handle non-recoverable errors in the code.   */  class LoggableException : public OusiaException { -private: -	/** -	 * Function used internally to build the formated message that should be -	 * reported to the runtime environment. -	 */ -	static std::string formatMessage(const std::string &msg, -	                                 const SourceLocation &loc); -  public:  	/**  	 * Reported error message. @@ -104,7 +96,7 @@ public:  	 */  	LoggableException(std::string msg,  	                  SourceLocation loc = SourceLocation{}) -	    : OusiaException(formatMessage(msg, loc)), +	    : OusiaException(msg),  	      msg(std::move(msg)),  	      loc(std::move(loc))  	{ @@ -128,15 +120,27 @@ public:  	 * Constructor of LoggableException for arbitrary position objects.  	 *  	 * @param msg is the actual log message. -	 * @param loc is a reference to a variable with position and context data. +	 * @param loc is a reference to a variable with location data.  	 */  	template <class LocationType> -	LoggableException(std::string msg, LocationType &loc) +	LoggableException(std::string msg, const LocationType &loc)  	    : LoggableException(std::move(msg), loc.getLocation())  	{  	}  	/** +	 * Constructor of LoggableException for arbitrary position objects. +	 * +	 * @param msg is the actual log message. +	 * @param loc is a pointe to a variable with location data. +	 */ +	template <class LocationType> +	LoggableException(std::string msg, const LocationType *loc) +	    : LoggableException(std::move(msg), loc->getLocation()) +	{ +	} + +	/**  	 * Returns the position at which the exception occured in the text.  	 *  	 * @return the position descriptor. diff --git a/src/core/common/Location.cpp b/src/core/common/Location.cpp new file mode 100644 index 0000000..7fe5ea5 --- /dev/null +++ b/src/core/common/Location.cpp @@ -0,0 +1,33 @@ +/* +    Ousía +    Copyright (C) 2014, 2015  Benjamin Paaßen, Andreas Stöckel + +    This program is free software: you can redistribute it and/or modify +    it under the terms of the GNU General Public License as published by +    the Free Software Foundation, either version 3 of the License, or +    (at your option) any later version. + +    This program is distributed in the hope that it will be useful, +    but WITHOUT ANY WARRANTY; without even the implied warranty of +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +    GNU General Public License for more details. + +    You should have received a copy of the GNU General Public License +    along with this program.  If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "Location.hpp" + +namespace ousia { + +/* Global Functions */ + +const SourceLocation NullSourceLocation; + +SourceContext NullSourceContextCallback(const SourceLocation &location) +{ +	return SourceContext{}; +} + +} + diff --git a/src/core/common/Location.hpp b/src/core/common/Location.hpp index 39e1011..808abbd 100644 --- a/src/core/common/Location.hpp +++ b/src/core/common/Location.hpp @@ -16,99 +16,379 @@      along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ +/** + * @file Location.hpp + * + * Types used for describing positions, ranges and excerpts of source files used + * for describing log messages. + * + * @author Andreas Stöckel (astoecke@techfak.uni-bielefeld.de) + */ +  #ifndef _OUSIA_LOCATION_HPP_  #define _OUSIA_LOCATION_HPP_ +#include <cstdint> +#include <functional> +#include <limits>  #include <string>  namespace ousia {  /** - * Struct representing a location within a source file. A position is defined by - * a byte offset (which is always reproducable), a line number and a column - * number (which may differ depending on the encoding used). + * Type used for referencing a source file currently opened in a Project. + */ +using SourceId = uint32_t; + +/** + * Maximum value for a SourceId. Indicates invalid entries. + */ +constexpr SourceId InvalidSourceId = std::numeric_limits<SourceId>::max(); + +/** + * Type used for specifying an offset within a source file. + */ +using SourceOffset = uint32_t; + +/** + * Maximum value for a SourceOffset. As SourceOffset is a 32 Bit unsigned + * integer, the maximum value is 2^32-1, which means that 4 GiB are addressable + * by SourceOffset. + */ +constexpr SourceOffset InvalidSourceOffset = +    std::numeric_limits<SourceOffset>::max(); + +/** + * Function for clamping a size_t to a valid SourceOffset value. + * + * @param pos is the size_t value that should be converted to a SourceOffset + * value. If pos is larger than the maximum value that can be represented by + * SourceOffset, the result is set to this maximum value, which is interpreted + * as "invalid" by functions dealing with the SourceOffset type. + * @return the clamped position value. + */ +inline SourceOffset clampToSourcePosition(size_t pos) +{ +	return pos > InvalidSourceOffset ? InvalidSourceOffset : pos; +} + +/** + * Class specifying a position within an (unspecified) source file.   */ -struct SourceLocation { +class SourcePosition { +private:  	/** -	 * Current line, starting with one. +	 * Offset position in bytes relative to the start of the document.  	 */ -	int line; +	SourceOffset pos; +public:  	/** -	 * Current column, starting with one. +	 * Default constructor of the SourcePosition class. Sets the position to +	 * InvalidSourceOffset and thus marks the SourcePosition as invalid.  	 */ -	int column; +	SourcePosition() : pos(InvalidSourceOffset) {}  	/** -	 * Current byte offset. +	 * Creates a new SourcePosition instance with the given byte offset.  	 */ -	size_t offs; +	SourcePosition(size_t pos) : pos(clampToSourcePosition(pos)) {}  	/** -	 * Default constructor of the SourceLocation struct, initializes all -	 * memebers with zero. +	 * Sets the position of the SourcePosition value to the given value. Clamps +	 * the given size_t to the valid range. +	 * +	 * @param pos is the position value that should be set.  	 */ -	SourceLocation() : line(0), column(0), offs(0) {} +	void setPosition(size_t pos) { this->pos = clampToSourcePosition(pos); }  	/** -	 * Creates a new SourceLocation struct with only a line and no column. +	 * Returns the position value. Only use the value if "valid" returns true.  	 * -	 * @param line is the line number. -	 * @param column is the column number. +	 * @return the current position.  	 */ -	SourceLocation(int line) : line(line), column(0), offs(0) {} +	SourceOffset getPosition() const { return pos; }  	/** -	 * Creates a new SourceLocation struct with a line and column. +	 * Returns true if the source position is valid, false otherwise. Invalid +	 * positions are set to the maximum representable number.  	 * -	 * @param line is the line number. -	 * @param column is the column number. +	 * @return true if the SourcePosition instance is value, false otherwise. +	 */ +	bool isValid() const { return pos != InvalidSourceOffset; } +}; + +/** + * The SourceRange class represents a range within an (unspecified) source file. + */ +class SourceRange { +private: +	/** +	 * Start byte offset. +	 */ +	SourcePosition start; + +	/** +	 * End byte offset.  	 */ -	SourceLocation(int line, int column) : line(line), column(column), offs(0) +	SourcePosition end; + +public: +	/** +	 * Default constructor. Creates an invalid range. +	 */ +	SourceRange(){}; + +	/** +	 * Constructor for a zero-length range. +	 * +	 * @param pos is the byte offset at which the SourceRange instance should be +	 * located. +	 */ +	SourceRange(SourcePosition pos) : start(pos), end(pos) {} + +	/** +	 * Constructor of a SourceRange instance. +	 * +	 * @param start is the byte offset of the first character in the range +	 * (start is inclusive). +	 * @param end points at the end of the range (end is non-inclusive). +	 */ +	SourceRange(SourcePosition start, SourcePosition end) +	    : start(start), end(end)  	{  	}  	/** -	 * Creates a new SourceLocation struct with a line, column and byte offset. +	 * Sets the start of the SourceRange value to the given value. This +	 * operation might render the SourceRange invalid (if the given position is +	 * larger than the end position). +	 * +	 * @param pos is the start position value that should be set. +	 */ +	void setStart(SourcePosition pos) { this->start = pos; } + +	/** +	 * Sets the end of the SourceRange value to the given value. This operation +	 * might render the SourceRange invalid (if the given position is smaller +	 * than the start position).  	 * -	 * @param line is the line number. -	 * @param column is the column number. -	 * @param offs is the byte offset. +	 * @param pos is the end position that should be set.  	 */ -	SourceLocation(int line, int column, size_t offs) -	    : line(line), column(column), offs(offs) +	void setEnd(SourcePosition pos) { this->end = pos; } + +	/** +	 * Sets the start and end of the SourceRange value to the given values. +	 * This operation might render the SourceRange invalid (if the given end +	 * position is smaller than the start position). +	 * +	 * @param start is the start position that should be set. +	 * @param end is the end position that should be set. +	 */ +	void setRange(SourcePosition start, SourcePosition end)  	{ +		this->start = start; +		this->end = end;  	}  	/** -	 * Returns true, if the line number is valid, false otherwise. +	 * Makes the Range represent a zero-length range that is located at the +	 * given position. The given position should be interpreted as being located +	 * "between the character just before the start offset and the start +	 * offset".  	 * -	 * @return true for valid line numbers. +	 * @param pos is the position to which start and end should be set.  	 */ -	bool hasLine() const { return line > 0; } +	void setPosition(SourcePosition pos) +	{ +		this->start = pos; +		this->end = pos; +	}  	/** -	 * Returns true, if the column number is valid, false otherwise. +	 * Returns the start position of the SourceRange instance.  	 * -	 * @return true for valid column numbers. +	 * @return the start offset in bytes. +	 */ +	SourceOffset getStart() const { return start.getPosition(); } + +	/** +	 * Returns the end position of the SourceRange instance. +	 * +	 * @return the end offset in bytes (non-inclusive). +	 */ +	SourceOffset getEnd() const { return end.getPosition(); } + +	/** +	 * Returns a copy of the underlying SourcePosition instance representing the +	 * start position. +	 * +	 * @return a copy of the start SourcePosition instance.  	 */ -	bool hasColumn() const { return column > 0; } +	SourcePosition getStartPosition() const { return start; }  	/** -	 * Returns true, if the position is valid, false otherwise. This function is -	 * equivalent to the hasLine() function. +	 * Returns a copy of the underlying SourcePosition instance representing the +	 * end position.  	 * -	 * @return true if the Position struct is valid. +	 * @return a copy of the end SourcePosition instance.  	 */ -	bool valid() const { return hasLine(); } +	SourcePosition getEndPosition() const { return end; } + +	/** +	 * Returns the length of the range. A range may have a zero value length, in +	 * which case it should be interpreted as "between the character before +	 * the start offset and the start offset". The returned value is only valid +	 * if the isValid() method returns true! +	 * +	 * @return the length of the range in bytes. +	 */ +	size_t getLength() const { return end.getPosition() - start.getPosition(); } + +	/** +	 * Returns true if this range is actually valid. This is the case if the +	 * start position is smaller or equal to the end position and start and end +	 * position themself are valid. +	 * +	 * @return true if the Range is valid. +	 */ +	bool isValid() const +	{ +		return start.isValid() && end.isValid() && +		       start.getPosition() <= end.getPosition(); +	}  };  /** + * The SourceLocation class describes a range within a specific source file. + */ +class SourceLocation : public SourceRange { +private: +	/** +	 * Id of the source file. +	 */ +	SourceId sourceId; + +public: +	/** +	 * Default constructor. +	 */ +	SourceLocation() : sourceId(InvalidSourceId){}; + +	/** +	 * Constructor, binds the SourceLocation to the given source file. +	 * +	 * @param sourceId specifies the file this location refers to. +	 */ +	SourceLocation(SourceId sourceId) : sourceId(sourceId){}; + +	/** +	 * Constructor for a zero-length range. +	 * +	 * @param sourceId specifies the file this location refers to. +	 * @param pos is the byte offset at which the SourceRange instance should be +	 * located. +	 */ +	SourceLocation(SourceId sourceId, SourcePosition pos) +	    : SourceRange(pos), sourceId(sourceId) +	{ +	} + +	/** +	 * Constructor of a SourceRange instance. +	 * +	 * @param sourceId specifies the file this location refers to. +	 * @param start is the byte offset of the first character in the range +	 * (start is inclusive). +	 * @param end points at the end of the range (end is non-inclusive). +	 */ +	SourceLocation(SourceId sourceId, SourcePosition start, SourcePosition end) +	    : SourceRange(start, end), sourceId(sourceId) +	{ +	} + +	/** +	 * Constructor of a SourceRange instance. +	 * +	 * @param sourceId specifies the file this location refers to. +	 * @param start is the byte offset of the first character in the range +	 * (start is inclusive). +	 * @param end points at the end of the range (end is non-inclusive). +	 */ +	SourceLocation(SourceId sourceId, const SourceRange &range) +	    : SourceRange(range), sourceId(sourceId) +	{ +	} + +	/** +	 * Sets the source id to the given value. +	 * +	 * @param sourceId specifies the file this location refers to. +	 */ +	void setSourceId(SourceId sourceId) { this->sourceId = sourceId; } + +	/** +	 * Returns the id of the source file this SourceLocation instance is bound +	 * to. +	 * +	 * @return the id of the source file this instance is bound to. +	 */ +	SourceId getSourceId() const { return sourceId; } + +	/** +	 * Returns true if this location is actually valid. This is the case if +	 * the underlying range is valid and the source id is valid. +	 * +	 * @return true if the Range is valid. +	 */ +	bool isValid() const +	{ +		return SourceRange::isValid() && sourceId != InvalidSourceId; +	} +}; + +/** + * NullSourceLocation is an empty SourceLocation instance. + */ +extern const SourceLocation NullSourceLocation; + +/**   * Represents the context of a SourceLocation instance. Used to build error   * messages.   */  struct SourceContext {  	/** +	 * Underlying source range (contains the byte start and end offsets in +	 * bytes). +	 */ +	SourceRange range; + +	/** +	 * Name of the underlying resource. +	 */ +	std::string filename; + +	/** +	 * Start line, starting with one. +	 */ +	int startLine; + +	/** +	 * Start column, starting with one. +	 */ +	int startColumn; + +	/** +	 * End line, starting with one. +	 */ +	int endLine; + +	/** +	 * End column, starting with one. +	 */ +	int endColumn; + +	/**  	 * Set to the content of the current line.  	 */  	std::string text; @@ -120,6 +400,12 @@ struct SourceContext {  	int relPos;  	/** +	 * Relative length (in characters) within that line. May end beyond the +	 * text given in the context. +	 */ +	int relLen; + +	/**  	 * Set to true if the beginning of the line has been truncated (because  	 * the reader position is too far away from the actual position of the  	 * line). @@ -134,39 +420,45 @@ struct SourceContext {  	bool truncatedEnd;  	/** -	 * Default constructor, initializes all members with zero values. +	 * Default constructor, initializes primitive members with zero values.  	 */  	SourceContext() -	    : text(), relPos(0), truncatedStart(false), truncatedEnd(false) +	    : startLine(0), +	      startColumn(0), +	      endLine(0), +	      endColumn(0), +	      relPos(0), +	      relLen(0), +	      truncatedStart(false), +	      truncatedEnd(false)  	{  	}  	/** -	 * Constructor of the SourceContext class. +	 * Returns true the context text is not empty.  	 * -	 * @param text is the current line the text cursor is at. -	 * @param relPos is the relative position of the text cursor within that -	 * line. -	 * @param truncatedStart specifies whether the text was truncated at the -	 * beginning. -	 * @param truncatedEnd specifies whether the text was truncated at the -	 * end. -	 */ -	SourceContext(std::string text, size_t relPos, bool truncatedStart, -	              bool truncatedEnd) -	    : text(std::move(text)), -	      relPos(relPos), -	      truncatedStart(truncatedStart), -	      truncatedEnd(truncatedEnd) -	{ -	} +	 * @return true if the context is valid and e.g. should be printed. +	 */ +	bool isValid() const { return range.isValid() && hasLine() && hasColumn(); }  	/** -	 * Returns true the context text is not empty. +	 * Returns true if a valid (non-empty) filename is set. +	 */ +	bool hasFile() const { return !filename.empty(); } + +	/** +	 * Returns true, if the start line number is valid, false otherwise.  	 * -	 * @return true if the context is valid and e.g. should be printed. +	 * @return true for valid line numbers. +	 */ +	bool hasLine() const { return startLine > 0; } + +	/** +	 * Returns true, if the start column number is valid, false otherwise. +	 * +	 * @return true for valid column numbers.  	 */ -	bool valid() const { return !text.empty(); } +	bool hasColumn() const { return startColumn > 0; }  };  /** @@ -174,10 +466,20 @@ struct SourceContext {   * location.   *   * @param location is the location for which the context should be looked up. - * @param data is used defined data associated with the callback. + * @return the corresponding SourceContext. + */ +using SourceContextCallback = +    std::function<SourceContext(const SourceLocation &)>; + +/** + * Function to be used as default value for the SourceContextCallback. Returns + * an invalid SourceContext. + * + * @param location is the location for which the context should be looked up. + * @return an empty, invalid SourceContext.   */ -using SourceContextCallback = SourceContext (*)(const SourceLocation &location, -                                                void *data); +SourceContext NullSourceContextCallback(const SourceLocation &location); +  }  #endif /* _OUSIA_LOCATION_HPP_ */ diff --git a/src/core/common/Logger.cpp b/src/core/common/Logger.cpp index fa4b5c8..034953d 100644 --- a/src/core/common/Logger.cpp +++ b/src/core/common/Logger.cpp @@ -27,10 +27,10 @@ namespace ousia {  /* Class Logger */  void Logger::log(Severity severity, const std::string &msg, -                 const SourceLocation &loc) +                 const SourceLocation &loc, MessageMode mode)  {  	// Assemble the message and pass it through the filter, then process it -	Message message { severity, std::move(msg), loc }; +	Message message{severity, mode, std::move(msg), loc};  	if (filterMessage(message)) {  		processMessage(message);  	} @@ -42,30 +42,43 @@ LoggerFork Logger::fork() { return LoggerFork(this); }  void LoggerFork::processMessage(const Message &msg)  { -	calls.push_back(Call(CallType::MESSAGE, messages.size())); +	calls.emplace_back(CallType::MESSAGE, messages.size());  	messages.push_back(msg);  } -void LoggerFork::processPushFile(const File &file) +void LoggerFork::processPushDefaultLocation(const SourceLocation &loc)  { -	calls.push_back(Call(CallType::PUSH_FILE, files.size())); -	files.push_back(file); +	calls.emplace_back(CallType::PUSH_LOCATION, locations.size()); +	locations.push_back(loc);  } -void LoggerFork::processPopFile() +void LoggerFork::processPopDefaultLocation()  { -	calls.push_back(Call(CallType::POP_FILE, 0)); +	calls.emplace_back(CallType::POP_LOCATION, 0);  }  void LoggerFork::processSetDefaultLocation(const SourceLocation &loc)  {  	// Check whether setDefaultLocation was called immediately before, if yes,  	// simply override the data -	if (!calls.empty() && calls.back().type == CallType::SET_DEFAULT_LOCATION) { +	if (!calls.empty() && calls.back().type == CallType::SET_LOCATION) {  		locations.back() = loc;  	} else { -		calls.push_back(Call(CallType::SET_DEFAULT_LOCATION, locations.size())); -		locations.push_back(loc); +		calls.emplace_back(CallType::SET_LOCATION, locations.size()); +		locations.emplace_back(loc); +	} +} + +void LoggerFork::processSetSourceContextCallback( +    SourceContextCallback sourceContextCallback) +{ +	// Check whether setSourceContextCallback was called immediately before, +	// if yes, simply override the data +	if (!calls.empty() && calls.back().type == CallType::SET_CONTEXT_CALLBACK) { +		callbacks.back() = sourceContextCallback; +	} else { +		calls.emplace_back(CallType::SET_CONTEXT_CALLBACK, callbacks.size()); +		callbacks.emplace_back(sourceContextCallback);  	}  } @@ -73,45 +86,91 @@ void LoggerFork::purge()  {  	calls.clear();  	messages.clear(); -	files.clear();  	locations.clear(); +	callbacks.clear();  }  void LoggerFork::commit()  {  	for (const Call &call : calls) {  		switch (call.type) { -			case CallType::MESSAGE: { +			case CallType::MESSAGE:  				if (parent->filterMessage(messages[call.dataIdx])) {  					parent->processMessage(messages[call.dataIdx]);  				}  				break; -			} -			case CallType::PUSH_FILE: { -				parent->processPushFile(files[call.dataIdx]); +			case CallType::PUSH_LOCATION: +				parent->processPushDefaultLocation(locations[call.dataIdx]);  				break; -			} -			case CallType::POP_FILE: -				parent->processPopFile(); +			case CallType::POP_LOCATION: +				parent->processPopDefaultLocation();  				break; -			case CallType::SET_DEFAULT_LOCATION: +			case CallType::SET_LOCATION:  				parent->processSetDefaultLocation(locations[call.dataIdx]);  				break; +			case CallType::SET_CONTEXT_CALLBACK: +				parent->processSetSourceContextCallback( +				    callbacks[call.dataIdx]); +				break;  		}  	}  	purge();  } -/* Class ConcreteLogger */ +/* Class ScopedLogger */ + +ScopedLogger::ScopedLogger(Logger &parent, SourceLocation loc) +    : parent(parent), depth(0) +{ +	pushDefaultLocation(loc); +} + +ScopedLogger::~ScopedLogger() +{ +	while (depth > 0) { +		popDefaultLocation(); +	} +} + +void ScopedLogger::processMessage(const Message &msg) +{ +	parent.processMessage(msg); +} + +bool ScopedLogger::filterMessage(const Message &msg) +{ +	return parent.filterMessage(msg); +} + +void ScopedLogger::processPushDefaultLocation(const SourceLocation &loc) +{ +	parent.processPushDefaultLocation(loc); +	depth++; +} + +void ScopedLogger::processPopDefaultLocation() +{ +	depth--; +	parent.processPopDefaultLocation(); +} -static const Logger::File EMPTY_FILE{"", SourceLocation{}, nullptr, nullptr}; +void ScopedLogger::processSetDefaultLocation(const SourceLocation &loc) +{ +	parent.processSetDefaultLocation(loc); +} -void ConcreteLogger::processPushFile(const File &file) +void ScopedLogger::processSetSourceContextCallback( +    SourceContextCallback sourceContextCallback)  { -	files.push_back(file); +	parent.processSetSourceContextCallback(sourceContextCallback);  } -void ConcreteLogger::processPopFile() { files.pop_back(); } +/* Class ConcreteLogger */ + +ConcreteLogger::ConcreteLogger(Severity minSeverity) +    : minSeverity(minSeverity), sourceContextCallback(NullSourceContextCallback) +{ +}  bool ConcreteLogger::filterMessage(const Message &msg)  { @@ -126,40 +185,46 @@ bool ConcreteLogger::filterMessage(const Message &msg)  	return sev >= static_cast<uint8_t>(minSeverity);  } -void ConcreteLogger::processSetDefaultLocation(const SourceLocation &loc) +void ConcreteLogger::processPushDefaultLocation(const SourceLocation &loc)  { -	defaultLocation = loc; +	locations.emplace_back(loc);  } -const Logger::File &ConcreteLogger::currentFile() const +void ConcreteLogger::processPopDefaultLocation()  { -	if (!files.empty()) { -		return files.back(); +	if (!locations.empty()) { +		locations.pop_back(); +	} +} + +void ConcreteLogger::processSetDefaultLocation(const SourceLocation &loc) +{ +	if (!locations.empty()) { +		locations.back() = loc; +	} else { +		locations.emplace_back(loc);  	} -	return EMPTY_FILE;  } -const std::string &ConcreteLogger::currentFilename() const +void ConcreteLogger::processSetSourceContextCallback( +    SourceContextCallback sourceContextCallback)  { -	return currentFile().file; +	this->sourceContextCallback = sourceContextCallback;  }  const SourceLocation &ConcreteLogger::messageLocation(const Message &msg) const  { -	if (msg.loc.valid()) { +	if (msg.loc.isValid()) {  		return msg.loc; +	} else if (!locations.empty()) { +		return locations.back();  	} -	return defaultLocation; +	return NullSourceLocation;  }  SourceContext ConcreteLogger::messageContext(const Message &msg) const  { -	const Logger::File &file = currentFile(); -	const SourceLocation &loc = messageLocation(msg); -	if (file.ctxCallback && loc.valid()) { -		return file.ctxCallback(loc, file.ctxCallbackData); -	} -	return SourceContext{}; +	return sourceContextCallback(messageLocation(msg));  }  Severity ConcreteLogger::getMaxEncounteredSeverity() @@ -183,8 +248,9 @@ size_t ConcreteLogger::getSeverityCount(Severity severity)  void ConcreteLogger::reset()  { -	files.clear(); +	locations.clear();  	messageCounts.clear(); +	sourceContextCallback = NullSourceContextCallback;  }  bool ConcreteLogger::hasError() @@ -193,6 +259,11 @@ bool ConcreteLogger::hasError()  	       getSeverityCount(Severity::FATAL_ERROR) > 0;  } +bool ConcreteLogger::hasFatalError() +{ +	return getSeverityCount(Severity::FATAL_ERROR) > 0; +} +  /* Class TerminalLogger */  void TerminalLogger::processMessage(const Message &msg) @@ -200,29 +271,26 @@ void TerminalLogger::processMessage(const Message &msg)  	Terminal t(useColor);  	// Fetch filename, position and context -	const std::string filename = currentFilename(); -	const SourceLocation pos = messageLocation(msg);  	const SourceContext ctx = messageContext(msg);  	// Print the file name -	bool hasFile = !filename.empty(); -	if (hasFile) { -		os << t.bright() << filename << t.reset(); +	if (ctx.hasFile()) { +		os << t.bright() << ctx.filename << t.reset();  	}  	// Print line and column number -	if (pos.hasLine()) { -		if (hasFile) { +	if (ctx.hasLine()) { +		if (ctx.hasFile()) {  			os << ':';  		} -		os << t.bright() << pos.line << t.reset(); -		if (pos.hasColumn()) { -			os << ':' << pos.column; +		os << t.bright() << ctx.startLine << t.reset(); +		if (ctx.hasColumn()) { +			os << ':' << ctx.startColumn;  		}  	}  	// Print the optional seperator -	if (hasFile || pos.hasLine()) { +	if (ctx.hasFile() || ctx.hasLine()) {  		os << ": ";  	} @@ -249,30 +317,30 @@ void TerminalLogger::processMessage(const Message &msg)  	os << msg.msg << std::endl;  	// Print the error message context if available -	if (ctx.valid()) { -		size_t relPos = ctx.relPos; -		if (ctx.truncatedStart) { -			os << "[...] "; -		} -		os << ctx.text; -		if (ctx.truncatedEnd) { -			os << " [...]"; -		} -		os << std::endl; - -		if (ctx.truncatedStart) { -			os << "      "; -		} - -		for (size_t i = 0; i < relPos; i++) { -			if (i < ctx.text.size() && ctx.text[i] == '\t') { -				os << '\t'; -			} else { -				os << ' '; -			} -		} -		os << t.color(Terminal::GREEN) << '^' << t.reset() << std::endl; -	} +	/*	if (ctx.valid()) { +	        size_t relPos = ctx.relPos; +	        if (ctx.truncatedStart) { +	            os << "[...] "; +	        } +	        os << ctx.text; +	        if (ctx.truncatedEnd) { +	            os << " [...]"; +	        } +	        os << std::endl; + +	        if (ctx.truncatedStart) { +	            os << "      "; +	        } + +	        for (size_t i = 0; i < relPos; i++) { +	            if (i < ctx.text.size() && ctx.text[i] == '\t') { +	                os << '\t'; +	            } else { +	                os << ' '; +	            } +	        } +	        os << t.color(Terminal::GREEN) << '^' << t.reset() << std::endl; +	    }*/  }  } diff --git a/src/core/common/Logger.hpp b/src/core/common/Logger.hpp index 767d8ab..85b1bb1 100644 --- a/src/core/common/Logger.hpp +++ b/src/core/common/Logger.hpp @@ -73,6 +73,40 @@ enum class Severity : uint8_t {  	FATAL_ERROR = 4  }; +/** + * Enum signifying how the message should be displayed. MessageMode constants + * can be combined using the bitwise or (|) operator. + */ +enum class MessageMode : uint8_t { +	/** +     * Default display mode. +     */ +	DEFAULT = 0, + +	/** +     * Do not display a context. +     */ +	NO_CONTEXT = 1, + +	/** +     * Do not display a file backtrace. +     */ +	NO_TRACE = 2 +}; + +/** + * Bitwise or for the MessageMode class. + * + * @param a is the first MessageMode. + * @param b is the second MessageMode. + * @return the two message modes combined using bitwise or. + */ +inline MessageMode operator|(MessageMode a, MessageMode b) +{ +	return static_cast<MessageMode>(static_cast<uint8_t>(a) | +	                                static_cast<uint8_t>(b)); +} +  // Forward declaration  class LoggerFork;  class ScopedLogger; @@ -92,51 +126,6 @@ public:  	friend ScopedLogger;  	/** -	 * Describes a file inclusion. -	 */ -	struct File { -		/** -		 * Current filename. -		 */ -		std::string file; - -		/** -		 * Location at which the file was included. -		 */ -		SourceLocation loc; - -		/** -		 * Callback used to retrieve the context for a certain location -		 */ -		SourceContextCallback ctxCallback; - -		/** -		 * Data to be passed to the callback. -		 */ -		void *ctxCallbackData; - -		/** -		 * Constructor of the Scope struct. -		 * -		 * @param type is the type of -		 * @param file is the name of the current file. -		 * @param loc is the location at which the file was included. -		 * @param ctxCallback is the callback function that should be called -		 * for looking up the context belonging to a SourceLocation instance. -		 * @param ctxCallbackData is additional data that should be passed to -		 * the callback function. -		 */ -		File(std::string file, SourceLocation loc, -		     SourceContextCallback ctxCallback, void *ctxCallbackData) -		    : file(std::move(file)), -		      loc(loc), -		      ctxCallback(ctxCallback), -		      ctxCallbackData(ctxCallbackData) -		{ -		} -	}; - -	/**  	 * The message struct represents a single log message and all information  	 * attached to it.  	 */ @@ -147,6 +136,11 @@ public:  		Severity severity;  		/** +		 * Message mode. +		 */ +		MessageMode mode; + +		/**  		 * Actual log message.  		 */  		std::string msg; @@ -157,15 +151,51 @@ public:  		SourceLocation loc;  		/** +		 * Default constructor of the Message struct. +		 */ +		Message() : severity(Severity::DEBUG), mode(MessageMode::DEFAULT) {} + +		/**  		 * Constructor of the Message struct.  		 *  		 * @param severity describes the message severity. +		 * @param mode is the mode in which the message should be displayed.  		 * @param msg contains the actual message. +		 * @param loc is the location at which the message should be displayed.  		 */ -		Message(Severity severity, std::string msg, const SourceLocation &loc) -		    : severity(severity), msg(std::move(msg)), loc(loc){}; +		Message(Severity severity, MessageMode mode, std::string msg, +		        const SourceLocation &loc) +		    : severity(severity), mode(mode), msg(std::move(msg)), loc(loc) +		{ +		}  	}; +	/** +	 * Calls the getLocation function on the given reference. +	 * +	 * @param obj is the object on which the getLocation function should be +	 * called. +	 * @return the SourceLocation returned by the getLocation function. +	 */ +	template <typename T> +	static SourceLocation location(const T &obj) +	{ +		return obj.getLocation(); +	} + +	/** +	 * Calls the getLocation function on the given pointer. +	 * +	 * @param obj is the object on which the getLocation function should be +	 * called. +	 * @return the SourceLocation returned by the getLocation function. +	 */ +	template <typename T> +	static SourceLocation location(const T *obj) +	{ +		return obj->getLocation(); +	} +  protected:  	/**  	 * Function to be overriden by child classes to actually display or store @@ -188,24 +218,37 @@ protected:  	virtual bool filterMessage(const Message &msg) { return true; }  	/** -	 * Called whenever a new file is pushed onto the stack. +	 * Called whenever the pushDefaultLocation function is called.  	 * -	 * @param file is the file structure that should be stored on the stack. +	 * @param loc is the default location that should be pushed onto the stack.  	 */ -	virtual void processPushFile(const File &file) {} +	virtual void processPushDefaultLocation(const SourceLocation &loc) {}  	/** -	 * Called whenever a scope is popped from the stack. +	 * Called whenever the popDefaultLocation function is called. +	 * +	 * @param loc is the default location that should be popped from the stack.  	 */ -	virtual void processPopFile() {} +	virtual void processPopDefaultLocation() {}  	/**  	 * Called whenever the setDefaultLocation function is called.  	 * -	 * @param loc is the default location that should be set. +	 * @param loc is the default location that shuold replace the current one on +	 * the stack.  	 */  	virtual void processSetDefaultLocation(const SourceLocation &loc) {} +	/** +	 * Called whenever the setSourceContextCallback function is called. +	 * +	 * @param sourceContextCallback is the callback function that should be set. +	 */ +	virtual void processSetSourceContextCallback( +	    SourceContextCallback sourceContextCallback) +	{ +	} +  public:  	/**  	 * Virtual destructor. @@ -228,28 +271,25 @@ public:  	 * @param severity is the severity of the log message.  	 * @param msg is the actual log message.  	 * @param loc is the location in the source file the message refers to. +	 * @param mode specifies how the message should be displayed.  	 */  	void log(Severity severity, const std::string &msg, -	         const SourceLocation &loc = SourceLocation{}); +	         const SourceLocation &loc = SourceLocation{}, +	         MessageMode mode = MessageMode::DEFAULT);  	/**  	 * Logs the given loggable exception.  	 *  	 * @param ex is the exception that should be logged. +	 * @param loc is a location which (if valid overrides the location given in +	 * the exception. +	 * @param mode specifies how the message should be displayed.  	 */ -	void log(const LoggableException &ex) +	void log(const LoggableException &ex, +	         const SourceLocation &loc = SourceLocation{}, +	         MessageMode mode = MessageMode::DEFAULT)  	{ -		log(Severity::ERROR, ex.msg, ex.getLocation()); -	} - -	/** -	 * Logs the given loggable exception at the given location. -	 * -	 * @param ex is the exception that should be logged. -	 */ -	void log(const LoggableException &ex, const SourceLocation &loc) -	{ -		log(Severity::ERROR, ex.msg, loc.valid() ? loc : ex.getLocation()); +		log(Severity::ERROR, ex.msg, loc.isValid() ? loc : ex.getLocation());  	}  	/** @@ -260,11 +300,13 @@ public:  	 * @param msg is the actual log message.  	 * @param loc is a reference to a variable which provides location  	 * information. +	 * @param mode specifies how the message should be displayed.  	 */  	template <class LocationType> -	void log(Severity severity, const std::string &msg, LocationType &loc) +	void log(Severity severity, const std::string &msg, LocationType loc, +	         MessageMode mode = MessageMode::DEFAULT)  	{ -		log(severity, msg, loc.getLocation()); +		log(severity, msg, location(loc), mode);  	}  	/** @@ -275,10 +317,11 @@ public:  	 * @param loc is the location in the source file the message refers to.  	 */  	void debug(const std::string &msg, -	           const SourceLocation &loc = SourceLocation{}) +	           const SourceLocation &loc = SourceLocation{}, +	           MessageMode mode = MessageMode::DEFAULT)  	{  #ifndef NDEBUG -		log(Severity::DEBUG, msg, loc); +		log(Severity::DEBUG, msg, loc, mode);  #endif  	} @@ -291,10 +334,11 @@ public:  	 * information.  	 */  	template <class LocationType> -	void debug(const std::string &msg, LocationType &loc) +	void debug(const std::string &msg, LocationType loc, +	           MessageMode mode = MessageMode::DEFAULT)  	{  #ifndef NDEBUG -		log(Severity::DEBUG, msg, loc); +		log(Severity::DEBUG, msg, loc, mode);  #endif  	} @@ -305,9 +349,10 @@ public:  	 * @param loc is the location in the source file the message refers to.  	 */  	void note(const std::string &msg, -	          const SourceLocation &loc = SourceLocation{}) +	          const SourceLocation &loc = SourceLocation{}, +	          MessageMode mode = MessageMode::DEFAULT)  	{ -		log(Severity::NOTE, msg, loc); +		log(Severity::NOTE, msg, loc, mode);  	}  	/** @@ -318,9 +363,10 @@ public:  	 * information.  	 */  	template <class LocationType> -	void note(const std::string &msg, LocationType &loc) +	void note(const std::string &msg, LocationType loc, +	          MessageMode mode = MessageMode::DEFAULT)  	{ -		log(Severity::NOTE, msg, loc); +		log(Severity::NOTE, msg, loc, mode);  	}  	/** @@ -330,9 +376,10 @@ public:  	 * @param loc is a reference to a variable which provides position  	 */  	void warning(const std::string &msg, -	             const SourceLocation &loc = SourceLocation{}) +	             const SourceLocation &loc = SourceLocation{}, +	             MessageMode mode = MessageMode::DEFAULT)  	{ -		log(Severity::WARNING, msg, loc); +		log(Severity::WARNING, msg, loc, mode);  	}  	/** @@ -343,9 +390,10 @@ public:  	 * information.  	 */  	template <class LocationType> -	void warning(const std::string &msg, LocationType &loc) +	void warning(const std::string &msg, LocationType loc, +	             MessageMode mode = MessageMode::DEFAULT)  	{ -		log(Severity::WARNING, msg, loc); +		log(Severity::WARNING, msg, location(loc), mode);  	}  	/** @@ -355,9 +403,10 @@ public:  	 * @param loc is a reference to a variable which provides position  	 */  	void error(const std::string &msg, -	           const SourceLocation &loc = SourceLocation{}) +	           const SourceLocation &loc = SourceLocation{}, +	           MessageMode mode = MessageMode::DEFAULT)  	{ -		log(Severity::ERROR, msg, std::move(loc)); +		log(Severity::ERROR, msg, loc, mode);  	}  	/** @@ -368,9 +417,10 @@ public:  	 * information.  	 */  	template <class LocationType> -	void error(const std::string &msg, LocationType &loc) +	void error(const std::string &msg, LocationType loc, +	           MessageMode mode = MessageMode::DEFAULT)  	{ -		log(Severity::ERROR, msg, loc); +		log(Severity::ERROR, msg, location(loc), mode);  	}  	/** @@ -380,9 +430,10 @@ public:  	 * @param loc is a reference to a variable which provides position  	 */  	void fatalError(const std::string &msg, -	                const SourceLocation &loc = SourceLocation{}) +	                const SourceLocation &loc = SourceLocation{}, +	                MessageMode mode = MessageMode::DEFAULT)  	{ -		log(Severity::FATAL_ERROR, msg, loc); +		log(Severity::FATAL_ERROR, msg, loc, mode);  	}  	/** @@ -393,41 +444,44 @@ public:  	 * information.  	 */  	template <class LocationType> -	void fatalError(const std::string &msg, LocationType &loc) +	void fatalError(const std::string &msg, LocationType loc, +	                MessageMode mode = MessageMode::DEFAULT)  	{ -		log(Severity::FATAL_ERROR, msg, loc); +		log(Severity::FATAL_ERROR, msg, location(loc), mode);  	}  	/** -	 * Pushes a new file name onto the internal filename stack. +	 * Sets the source context callback to be used to resolve SourceLocation +	 * instances to SourceContext instances. The sourceContextCallback should be +	 * set as early as possible when using the logger.  	 * -	 * @param name is the name of the file to be added to the stack. -	 * @param loc is the position from which the new file is included. -	 * @param ctxCallback is the callback function that should be called if a -	 * SourceLocation needs to be resolved to a SourceContext. -	 * @param ctxCallbackData is the data that should be passed to the callback. +	 * @param sourceContextCallback is the new sourceContextCallback to be used.  	 */ -	void pushFile(std::string name, SourceLocation loc = SourceLocation{}, -	              SourceContextCallback ctxCallback = nullptr, -	              void *ctxCallbackData = nullptr) +	void setSourceContextCallback(SourceContextCallback sourceContextCallback)  	{ -		processPushFile( -		    File(std::move(name), loc, ctxCallback, ctxCallbackData)); +		processSetSourceContextCallback(sourceContextCallback);  	}  	/** -	 * Pops the filename from the internal filename stack. Resets any location -	 * set by the setDefaultLocation() method. +	 * Pushes a new default location onto the default location stack. +	 * +	 * @param loc is the location that should be used if no (valid) location is +	 * specified in the Logger.  	 */ -	void popFile() +	void pushDefaultLocation(const SourceLocation &loc)  	{ -		processPopFile(); -		resetDefaultLocation(); +		processPushDefaultLocation(loc);  	}  	/** -	 * Sets the default location. The default location is automatically reset -	 * once the popFile() method is called. +	 * Pops the last default location from the default location stack. +	 */ +	void popDefaultLocation() { processPopDefaultLocation(); } + +	/** +	 * Replaces the topmost default location on the location stack with the +	 * given location. Creates a new entry in the location stack if the stack +	 * was empty.  	 *  	 * @param loc is the location that should be used if no (valid) location is  	 * specified in the Logger. @@ -438,12 +492,6 @@ public:  	}  	/** -	 * Resets the default location, a previously set default location will be -	 * no longer used. -	 */ -	void resetDefaultLocation() { processSetDefaultLocation(SourceLocation{}); } - -	/**  	 * Returns a forked logger instance which can be used to collect log  	 * messages for which it is not sure whether they will be used.  	 * @@ -469,7 +517,13 @@ private:  	/**  	 * Intanally used to store the incomming function calls.  	 */ -	enum class CallType { MESSAGE, PUSH_FILE, POP_FILE, SET_DEFAULT_LOCATION }; +	enum class CallType { +		MESSAGE, +		PUSH_LOCATION, +		POP_LOCATION, +		SET_LOCATION, +		SET_CONTEXT_CALLBACK +	};  	/**  	 * Datastructure used to represent a logger function call. @@ -506,14 +560,14 @@ private:  	std::vector<Message> messages;  	/** -	 * Vector storing all incomming pushed Scope instances. +	 * Vector storing all incomming location instances.  	 */ -	std::vector<File> files; +	std::vector<SourceLocation> locations;  	/** -	 * Vector storing all incomming location instances. +	 * Vector storing all incomming source context callbacks.  	 */ -	std::vector<SourceLocation> locations; +	std::vector<SourceContextCallback> callbacks;  	/**  	 * Parent logger instance. @@ -529,17 +583,19 @@ private:  protected:  	void processMessage(const Message &msg) override; -	void processPushFile(const File &file) override; -	void processPopFile() override; +	void processPushDefaultLocation(const SourceLocation &loc) override; +	void processPopDefaultLocation() override;  	void processSetDefaultLocation(const SourceLocation &loc) override; +	void processSetSourceContextCallback( +	    SourceContextCallback sourceContextCallback) override;  public:  	// Default move constructor  	LoggerFork(LoggerFork &&l)  	    : calls(std::move(l.calls)),  	      messages(std::move(l.messages)), -	      files(std::move(l.files)),  	      locations(std::move(l.locations)), +	      callbacks(std::move(l.callbacks)),  	      parent(std::move(l.parent)){};  	/** @@ -578,93 +634,54 @@ protected:  	 *  	 * @param msg is the message to be relayed to the parent logger.  	 */ -	void processMessage(const Message &msg) override -	{ -		parent.processMessage(msg); -	} +	void processMessage(const Message &msg) override;  	/**  	 * Relays the filterMessage call to the parent logger.  	 *  	 * @param msg is the message to be relayed to the parent logger.  	 */ -	bool filterMessage(const Message &msg) override -	{ -		return parent.filterMessage(msg); -	} +	bool filterMessage(const Message &msg) override;  	/** -	 * Relays the processPushFile call to the parent logger and increments the -	 * stack depth counter. -	 * -	 * @param file is the File instance to be relayed to the parent logger. +	 * Relays the processPushDefaultLocation call to the parent logger and +	 * increments the stack depth counter.  	 */ -	void processPushFile(const File &file) -	{ -		parent.processPushFile(file); -		depth++; -	} +	void processPushDefaultLocation(const SourceLocation &loc) override;  	/** -	 * Relays the processPopFile call to the parent logger and decrements the -	 * stack depth counter. +	 * Relays the processPopDefaultLocation call to the parent logger and +	 * decrements the stack depth counter.  	 */ -	void processPopFile() -	{ -		depth--; -		parent.processPopFile(); -	} +	void processPopDefaultLocation() override;  	/**  	 * Relays the processSetDefaultLocation call to the parent logger. -	 * -	 * @param loc is the location to be passed to the parent logger.  	 */ -	void processSetDefaultLocation(const SourceLocation &loc) -	{ -		parent.processSetDefaultLocation(loc); -	} +	void processSetDefaultLocation(const SourceLocation &loc) override; -public:  	/** -	 * Constructor of the ScopedLogger class. -	 * -	 * @param parent is the parent logger instance to which all calls should -	 * be relayed. +	 * Relays the processSetSourceContextCallback call to the parent logger.  	 */ -	ScopedLogger(Logger &parent) : Logger(), parent(parent) {} +	void processSetSourceContextCallback( +	    SourceContextCallback sourceContextCallback) override; +public:  	/**  	 * Constructor of the ScopedLogger class, pushes a first file instance onto  	 * the file stack.  	 *  	 * @param parent is the parent logger instance to which all calls should  	 * be relayed. -	 * @param name is the name of the file to be added to the stack. -	 * @param loc is the position from which the new file is included. -	 * @param ctxCallback is the callback function that should be called if a -	 * SourceLocation needs to be resolved to a SourceContext. -	 * @param ctxCallbackData is the data that should be passed to the callback. -	 */ -	ScopedLogger(Logger &parent, std::string name, -	             SourceLocation loc = SourceLocation{}, -	             SourceContextCallback ctxCallback = nullptr, -	             void *ctxCallbackData = nullptr) -	    : Logger(), parent(parent), depth(0) -	{ -		pushFile(name, loc, ctxCallback, ctxCallbackData); -	} +	 * @param loc specifies the first source location. +	 */ +	ScopedLogger(Logger &parent, SourceLocation loc = SourceLocation{});  	/**  	 * Destructor of the ScopedLogger class, automatically unwinds the file  	 * stack.  	 */ -	~ScopedLogger() -	{ -		while (depth > 0) { -			processPopFile(); -		} -	} +	~ScopedLogger();  };  /** @@ -681,7 +698,7 @@ protected:  	{  		if (msg.severity == Severity::ERROR ||  		    msg.severity == Severity::FATAL_ERROR) { -			throw LoggableException(msg.msg); +			throw LoggableException(msg.msg, msg.loc);  		}  	}  }; @@ -701,14 +718,14 @@ constexpr Severity DEFAULT_MIN_SEVERITY = Severity::DEBUG;  class ConcreteLogger : public Logger {  private:  	/** -	 * Stack containing the current file instance. +	 * Vector used to store the counts of each message type.  	 */ -	std::vector<File> files; +	std::vector<size_t> messageCounts;  	/** -	 * Vector used to store the counts of each message type. +	 * Vector used to store the current default locations.  	 */ -	std::vector<size_t> messageCounts; +	std::vector<SourceLocation> locations;  	/**  	 * Minimum severity to be used for filtering messages. @@ -716,9 +733,9 @@ private:  	Severity minSeverity;  	/** -	 * Current default location. +	 * Current source context callback.  	 */ -	SourceLocation defaultLocation; +	SourceContextCallback sourceContextCallback;  protected:  	/** @@ -730,25 +747,11 @@ protected:  	 */  	bool filterMessage(const Message &msg) override; -	/** -	 * Pushes the given file descriptor onto the internal file stack. -	 * -	 * @param file is the File descriptor to be pushed onto the internal file -	 * stack. -	 */ -	void processPushFile(const File &file) override; - -	/** -	 * Pops the given file descriptor from the internal file stack. -	 */ -	void processPopFile() override; - -	/** -	 * Sets the default location. -	 * -	 * @param loc is the new default location. -	 */ +	void processPushDefaultLocation(const SourceLocation &loc) override; +	void processPopDefaultLocation() override;  	void processSetDefaultLocation(const SourceLocation &loc) override; +	void processSetSourceContextCallback( +	    SourceContextCallback sourceContextCallback) override;  public:  	/** @@ -757,24 +760,7 @@ public:  	 * @param minSeverity is the severity below which message should be  	 * discarded.  	 */ -	ConcreteLogger(Severity minSeverity = DEFAULT_MIN_SEVERITY) -	    : minSeverity(minSeverity) -	{ -	} - -	/** -	 * Returns the name of the current file or an empty instance of the File -	 * instance if no current file is available. -	 * -	 * @return the name of the current file. -	 */ -	const File ¤tFile() const; - -	/** -	 * Returns the current filename or an empty string if no surch file is -	 * available. -	 */ -	const std::string ¤tFilename() const; +	ConcreteLogger(Severity minSeverity = DEFAULT_MIN_SEVERITY);  	/**  	 * Returns the current cursor location. @@ -800,7 +786,8 @@ public:  	/**  	 * Returns the number of messages for the given severity.  	 * -	 * @param severity is the log severity for which the message count should +	 * @param severity is the log severity for which the message count +	 *should  	 * be returned.  	 * @return the number of messages for this severity. Returns zero for  	 * invalid arguments. @@ -808,22 +795,32 @@ public:  	size_t getSeverityCount(Severity severity);  	/** -	 * Resets the statistics gathered by the ConcreteLogger instance (the number +	 * Resets the statistics gathered by the ConcreteLogger instance (the +	 * number  	 * of messages per log severity) and the internal file stack.  	 */  	void reset();  	/** -	 * Returns true if at least one message with either a fatal error or error -	 * severity was logged. +	 * Returns true if at least one message with either a fatal error or +	 * error severity was logged.  	 *  	 * @return true if an error or fatal error was logged.  	 */  	bool hasError(); + +	/** +	 * Returns true if at least one message with either a fatal error was +	 * logged. +	 * +	 * @return true if a fatal error was logged. +	 */ +	bool hasFatalError();  };  /** - * Class extending the Logger class and printing the log messages to the given + * Class extending the Logger class and printing the log messages to the + * given   * stream.   */  class TerminalLogger : public ConcreteLogger { @@ -850,7 +847,8 @@ public:  	 * Should be set to std::cerr in most cases.  	 * @param useColor if true, the TerminalLogger class will do its best to  	 * use ANSI/VT100 control sequences for colored log messages. -	 * @param minSeverity is the minimum severity below which log messages are +	 * @param minSeverity is the minimum severity below which log messages +	 *are  	 * discarded.  	 */  	TerminalLogger(std::ostream &os, bool useColor = false, diff --git a/src/core/common/Rtti.cpp b/src/core/common/Rtti.cpp index 17b8880..6849a0e 100644 --- a/src/core/common/Rtti.cpp +++ b/src/core/common/Rtti.cpp @@ -47,8 +47,8 @@ const Rtti &RttiStore::lookup(const std::type_info &native)  /* Class RttiBuilderBase */ -RttiBuilderBase &RttiBuilderBase::genericMethod(const std::string &name, -                                        std::shared_ptr<Function> function) +RttiBuilderBase &RttiBuilderBase::genericMethod( +    const std::string &name, std::shared_ptr<Function> function)  {  	if (!methods.emplace(name, function).second) {  		throw OusiaException(std::string("Method with name \"") + name + @@ -80,10 +80,11 @@ void Rtti::initialize() const  		// Register the parent properties and methods  		{ -			for (const Rtti *parent: parents) { +			for (const Rtti *parent : parents) {  				parent->initialize();  				methods.insert(parent->methods.begin(), parent->methods.end()); -				properties.insert(parent->properties.begin(), parent->properties.end()); +				properties.insert(parent->properties.begin(), +				                  parent->properties.end());  			}  		} @@ -125,18 +126,41 @@ bool Rtti::isa(const Rtti &other) const  	return parents.count(&other) > 0;  } +bool Rtti::isOneOf(const RttiSet &others) const +{ +	initialize(); +	for (const Rtti *other : others) { +		if (parents.count(other) > 0) { +			return true; +		} +	} +	return false; +} + +bool Rtti::setIsOneOf(const RttiSet &s1, const RttiSet &s2) +{ +	for (const Rtti *t1 : s1) { +		if (t1->isOneOf(s2)) { +			return true; +		} +	} +	return false; +} +  bool Rtti::composedOf(const Rtti &other) const  {  	initialize();  	return compositeTypes.count(&other) > 0;  } -const RttiMethodMap &Rtti::getMethods() const { +const RttiMethodMap &Rtti::getMethods() const +{  	initialize();  	return methods;  } -const RttiPropertyMap &Rtti::getProperties() const { +const RttiPropertyMap &Rtti::getProperties() const +{  	initialize();  	return properties;  } @@ -151,7 +175,8 @@ std::shared_ptr<Function> Rtti::getMethod(const std::string &name) const  	return it->second;  } -std::shared_ptr<PropertyDescriptor> Rtti::getProperty(const std::string &name) const +std::shared_ptr<PropertyDescriptor> Rtti::getProperty( +    const std::string &name) const  {  	initialize();  	auto it = properties.find(name); diff --git a/src/core/common/Rtti.hpp b/src/core/common/Rtti.hpp index 8350cb5..05cc728 100644 --- a/src/core/common/Rtti.hpp +++ b/src/core/common/Rtti.hpp @@ -378,10 +378,31 @@ public:  	 *  	 * @param other is the other type for which the relation to this type  	 * should be checked. +	 * @return true if this type (directly or indirectly) has the given other +	 * type as parent or equals the other type.  	 */  	bool isa(const Rtti &other) const;  	/** +	 * Returns true if this Rtti instance is one of the given types. +	 * +	 * @param others is a set of other types to be checked. +	 * @return true if this type (directly or indirectly) has once of the given +	 * other types as parent or equals one of the other types. +	 */ +	bool isOneOf(const RttiSet &others) const; + +	/** +	 * Checks whether any type in the first set is one type in the second set. +	 * +	 * @param s1 is the first set. For each type in this set we check whether +	 * it is one of the types in s2. +	 * @param s2 is the second set. +	 * @return true if the above condition is fulfilled, false otherwise. +	 */ +	static bool setIsOneOf(const RttiSet &s1, const RttiSet &s2); + +	/**  	 * Returns true if an instance of this type may have references to the other  	 * given type. This mechanism is used to prune impossible paths when  	 * resolving objects of a certain type by name in an object graph. diff --git a/src/core/common/Utils.cpp b/src/core/common/Utils.cpp index 5fde29c..c8fcdc6 100644 --- a/src/core/common/Utils.cpp +++ b/src/core/common/Utils.cpp @@ -17,7 +17,9 @@  */  #include <algorithm> +#include <cctype>  #include <limits> +#include <string>  #include "Utils.hpp" @@ -74,5 +76,26 @@ std::vector<std::string> Utils::split(const std::string &s, char delim)  	return res;  } +std::string Utils::toLower(std::string s) +{ +	std::transform(s.begin(), s.end(), s.begin(), tolower); +	return s; +} + +std::string Utils::extractFileExtension(const std::string &filename) +{ +	size_t n = 0; +	for (ssize_t i = filename.size() - 1; i >= 0; i--) { +		if (filename[i] == '/' || filename[i] == '\\') { +			return std::string{}; +		} +		if (filename[i] == '.') { +			return toLower(filename.substr(i + 1, n)); +		} +		n++; +	} +	return std::string{}; +} +  } diff --git a/src/core/common/Utils.hpp b/src/core/common/Utils.hpp index 1f7f142..22e0fd3 100644 --- a/src/core/common/Utils.hpp +++ b/src/core/common/Utils.hpp @@ -114,6 +114,26 @@ public:  	 * @return a vector of strings containing the splitted sub-strings.  	 */  	static std::vector<std::string> split(const std::string &s, char delim); + +	/** +	 * Converts the given string to lowercase (only works for ANSI characters). +	 * +	 * @param s is the string that should be converted to lowercase. +	 * @return s in lowercase. +	 */ +	static std::string toLower(std::string s); + +	/** +	 * Reads the file extension of the given filename. +	 * +	 * @param filename is the filename from which the extension should be +	 * extracted. +	 * @return the extension, excluding any leading dot. The extension is +	 * defined as the substring after the last dot in the given string, if the +	 * dot is after a slash or backslash. The extension is converted to +	 * lowercase. +	 */ +	static std::string extractFileExtension(const std::string &filename);  };  } diff --git a/src/core/managed/Managed.hpp b/src/core/managed/Managed.hpp index 08158b2..8b594ea 100644 --- a/src/core/managed/Managed.hpp +++ b/src/core/managed/Managed.hpp @@ -143,14 +143,46 @@ public:  	/* Data store methods */ +	/** +	 * Stores arbitrary data under the given key. Data will be overriden. This +	 * function can be used to attach data to the Managed object. +	 * +	 * @param key is an arbitrary string key that under which the data should +	 * be stored. +	 * @param h is the data that should be stored. +	 */  	void storeData(const std::string &key, Handle<Managed> h); +	/** +	 * Returns true if data was stored under the given key. +	 * +	 * @return true if data was stored under the given key, false otherwise. +	 */  	bool hasDataKey(const std::string &key); +	/** +	 * Returns data previously stored under the given key. +	 * +	 * @param key is the key specifying the slot from which the data should be +	 * read. +	 * @return previously stored data or nullptr if no data was stored for this +	 * key. +	 */  	Rooted<Managed> readData(const std::string &key); +	/** +	 * Returns a copy of all data that was attached to the node. +	 * +	 * @return a map between keys and stored data. +	 */  	std::map<std::string, Rooted<Managed>> readData(); +	/** +	 * Deletes all data entries with the given key from the node. +	 * +	 * @param key is the key specifying the slot that should be deleted. +	 * @return true if the operation was successful, false otherwise. +	 */  	bool deleteData(const std::string &key);  	/* Event handling methods */ diff --git a/src/core/managed/Manager.hpp b/src/core/managed/Manager.hpp index fec4bd1..7a93736 100644 --- a/src/core/managed/Manager.hpp +++ b/src/core/managed/Manager.hpp @@ -320,7 +320,8 @@ public:  	 * references.  	 *  	 * @param uid is the unique id for which the object should be returned. -	 * @return a pointer to the object with the given uid. +	 * @return a pointer to the object with the given uid or nullptr if the +	 * object no longer exists.  	 */  	Managed * getManaged(ManagedUid uid); diff --git a/src/core/model/Node.cpp b/src/core/model/Node.cpp index eb0e4a7..dbc85e2 100644 --- a/src/core/model/Node.cpp +++ b/src/core/model/Node.cpp @@ -363,7 +363,7 @@ bool Node::checkDuplicate(Handle<Node> elem,  		logger.error(std::string("Element with name \"") + name +  		             std::string("\" defined multiple times in parent ") +  		             type().name + std::string(" \"") + -		             Utils::join(path(), ".") + std::string("\"")); +		             Utils::join(path(), ".") + std::string("\""), *elem);  		return false;  	}  	return true; @@ -375,7 +375,7 @@ bool Node::validateName(Logger &logger) const  {  	if (!Utils::isIdentifier(name)) {  		logger.error(type().name + std::string(" name \"") + name + -		             std::string("\" is not a valid identifier")); +		             std::string("\" is not a valid identifier"), this);  		return false;  	}  	return true; diff --git a/src/core/model/Node.hpp b/src/core/model/Node.hpp index c5761a8..6fc7dba 100644 --- a/src/core/model/Node.hpp +++ b/src/core/model/Node.hpp @@ -35,6 +35,7 @@  #include <string>  #include <vector> +#include <core/common/Location.hpp>  #include <core/managed/Managed.hpp>  #include <core/managed/ManagedContainer.hpp> @@ -137,6 +138,12 @@ private:  	Owned<Node> parent;  	/** +	 * Location from which the node was read (specifies the source file and the +	 * range in that source file). +	 */ +	SourceLocation location; + +	/**  	 * A "dirty" flag that signifies if this Node has been already validated  	 * or not.  	 */ @@ -519,6 +526,21 @@ public:  	 * @return the current ValidationState of this Node.  	 */  	ValidationState getValidationState() const { return validationState; } + +	/** +	 * Returns the location in the source file. +	 * +	 * @return a source location descriptor. +	 */ +	SourceLocation getLocation() const { return location; } + +	/** +	 * Sets the location of the node to the given value. +	 * +	 * @param location describes the exact position of the Node in a source +	 * file. +	 */ +	void setLocation(const SourceLocation &location) {this->location = location;}  };  /** diff --git a/src/core/model/Project.cpp b/src/core/model/Project.cpp index fc08660..a0f1f08 100644 --- a/src/core/model/Project.cpp +++ b/src/core/model/Project.cpp @@ -30,17 +30,13 @@ namespace model {  Project::Project(Manager &mgr)      : Node(mgr),        systemTypesystem(acquire(new SystemTypesystem(mgr))), -      documents(this), -      domains(this), -      typesystems(this) +      documents(this)  {  }  bool Project::doValidate(Logger &logger) const  { -	return continueValidation(documents, logger) & -	       continueValidation(domains, logger) & -	       continueValidation(typesystems, logger); +	return continueValidation(documents, logger);  }  Rooted<SystemTypesystem> Project::getSystemTypesystem() @@ -50,16 +46,8 @@ Rooted<SystemTypesystem> Project::getSystemTypesystem()  Rooted<Typesystem> Project::createTypesystem(const std::string &name)  { -	Rooted<Typesystem> typesystem{ +	return Rooted<Typesystem>{  	    new Typesystem{getManager(), systemTypesystem, name}}; -	addTypesystem(typesystem); -	return typesystem; -} - -void Project::addTypesystem(Handle<Typesystem> typesystem) -{ -	invalidate(); -	typesystems.push_back(typesystem);  }  Rooted<Document> Project::createDocument(const std::string &name) @@ -69,39 +57,25 @@ Rooted<Document> Project::createDocument(const std::string &name)  	return document;  } -void Project::addDocument(Handle<Document> document) -{ -	invalidate(); -	documents.push_back(document); -} -  Rooted<Domain> Project::createDomain(const std::string &name)  { -	Rooted<Domain> domain{new Domain(getManager(), systemTypesystem, name)}; -	addDomain(domain); -	return domain; +	return Rooted<Domain>{new Domain(getManager(), systemTypesystem, name)};  } -void Project::addDomain(Handle<Domain> domain) +void Project::addDocument(Handle<Document> document)  {  	invalidate(); -	domains.push_back(domain); +	documents.push_back(document);  } -const NodeVector<Document> &Project::getDocuments() { return documents; } - -const NodeVector<Domain> &Project::getDomains() { return domains; } - -const NodeVector<Typesystem> &Project::getTypesystems() { return typesystems; } +const NodeVector<Document> &Project::getDocuments() const { return documents; }  }  namespace RttiTypes {  const Rtti Project = RttiBuilder<model::Project>("Project")                           .parent(&Node)                           .composedOf(&Document) -                         .composedOf(&Typesystem) -                         .composedOf(&SystemTypesystem) -                         .composedOf(&Domain); +                         .composedOf(&SystemTypesystem);  }  } diff --git a/src/core/model/Project.hpp b/src/core/model/Project.hpp index 576bd60..4e2a43b 100644 --- a/src/core/model/Project.hpp +++ b/src/core/model/Project.hpp @@ -44,8 +44,9 @@ class Document;  class Domain;  /** - * The Project class constitutes the top-level node in which documents, domains, - * typesystems and other resources are embedded. + * The Project class constitutes the top-level node in which a collection of + * documents are stored. It also contains an instance of the SystemTypesystem + * and allows for simple creation of new Typesystem and Domain instances.   */  class Project : public Node {  private: @@ -60,16 +61,6 @@ private:  	 */  	NodeVector<Document> documents; -	/** -	 * List containing all loaded domains. -	 */ -	NodeVector<Domain> domains; - -	/** -	 * List containing all loaded typesystems. -	 */ -	NodeVector<Typesystem> typesystems; -  protected:  	/**  	 * Validates the project and all parts it consists of. @@ -103,13 +94,6 @@ public:  	Rooted<Typesystem> createTypesystem(const std::string &name);  	/** -	 * Adds a single new typesystem to the project. -	 * -	 * @param typesystem is the typesystem that should be added to the project. -	 */ -	void addTypesystem(Handle<Typesystem> typesystem); - -	/**  	 * Returns a new document with the given name and adds it to the list of  	 * documents.  	 * @@ -118,13 +102,6 @@ public:  	Rooted<Document> createDocument(const std::string &name);  	/** -	 * Adds the given document to the list of documents in the project. -	 * -	 * @param document is the document that should be added to the project. -	 */ -	void addDocument(Handle<Document> document); - -	/**  	 * Returns a new domain with the given name and adds it to the list of  	 * domains. Provides a reference of the system typesystem to the domain.  	 * @@ -133,32 +110,18 @@ public:  	Rooted<Domain> createDomain(const std::string &name);  	/** -	 * Adds the given domain to the list of domains in the project. +	 * Adds the given document to the list of documents in the project.  	 * -	 * @param domain is the document that should be added to the project. +	 * @param document is the document that should be added to the project.  	 */ -	void addDomain(Handle<Domain> domain); +	void addDocument(Handle<Document> document);  	/**  	 * Returns all documents of this project.  	 *  	 * @return a reference pointing at the document list.  	 */ -	const NodeVector<Document> &getDocuments(); - -	/** -	 * Returns all domains of this project. -	 * -	 * @return a reference pointing at the domain list. -	 */ -	const NodeVector<Domain> &getDomains(); - -	/** -	 * Returns all typesystems of this project. -	 * -	 * @return a reference pointing at the typesystem list. -	 */ -	const NodeVector<Typesystem> &getTypesystems(); +	const NodeVector<Document> &getDocuments() const;  };  } diff --git a/src/core/parser/Parser.cpp b/src/core/parser/Parser.cpp index b5d9656..2978669 100644 --- a/src/core/parser/Parser.cpp +++ b/src/core/parser/Parser.cpp @@ -16,16 +16,23 @@      along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ +#include <core/common/CharReader.hpp> +  #include "Parser.hpp"  namespace ousia { -namespace parser { + +/* Class Parser */ + +Rooted<Node> Parser::parse(CharReader &reader, ParserContext &ctx) +{ +	return doParse(reader, ctx); +}  Rooted<Node> Parser::parse(const std::string &str, ParserContext &ctx)  {  	CharReader reader{str}; -	return parse(reader, ctx); -} +	return doParse(reader, ctx);  }  } diff --git a/src/core/parser/Parser.hpp b/src/core/parser/Parser.hpp index 049ee4e..e4419f5 100644 --- a/src/core/parser/Parser.hpp +++ b/src/core/parser/Parser.hpp @@ -32,94 +32,52 @@  #include <set>  #include <string> -#include <core/Registry.hpp> -#include <core/common/CharReader.hpp> -#include <core/common/Exceptions.hpp> -#include <core/common/Logger.hpp> +#include <core/managed/Managed.hpp>  #include <core/model/Node.hpp> -#include <core/model/Project.hpp> - -#include "Scope.hpp"  namespace ousia { -namespace parser { -// TODO: Implement a proper Mimetype class +// Forward declarations +class CharReader; +class ParserContext;  /** - * Struct containing the objects that are passed to a parser instance. + * Abstract parser class. This class builds the basic interface that should be + * used by any parser which reads data from an input stream and transforms it + * into an Ousía node graph.   */ -struct ParserContext { -	/** -	 * Reference to the Scope instance that should be used within the parser. -	 */ -	Scope &scope; - -	/** -	 * Reference to the Registry instance that should be used within the parser. -	 */ -	Registry ®istry; - -	/** -	 * Reference to the Logger the parser should log any messages to. -	 */ -	Logger &logger; - +class Parser { +protected:  	/** -	 * Reference to the Manager the parser should append nodes to. +	 * Parses the given input stream and returns a corresponding node for +	 * inclusion in the document graph. This method should be overridden by +	 * derived classes. +	 * +	 * @param reader is a reference to the CharReader that should be used. +	 * @param ctx is a reference to the context that should be used while +	 * parsing the document. +	 * @return a reference to the node representing the subgraph that has been +	 * created. The resulting node may point at not yet resolved entities, the +	 * calling code will try to resolve these. If no valid node can be produced, +	 * a corresponding LoggableException must be thrown by the parser.  	 */ -	Manager &manager; +	virtual Rooted<Node> doParse(CharReader &reader, ParserContext &ctx) = 0; +public:  	/** -	 * Project instance into which the new content should be parsed. +	 * Default constructor.  	 */ -	Rooted<model::Project> project; +	Parser() {}  	/** -	 * Constructor of the ParserContext class. -	 * -	 * @param scope is a reference to the Scope instance that should be used to -	 * lookup names. -	 * @param registry is a reference at the Registry class, which allows to -	 * obtain references at parsers for other formats or script engine -	 * implementations. -	 * @param logger is a reference to the Logger instance that should be used -	 * to log error messages and warnings that occur while parsing the document. -	 * @param manager is a Reference to the Manager the parser should append -	 * nodes to. -	 * @param project is the project into which the content should be parsed. +	 * No copy construction.  	 */ -	ParserContext(Scope &scope, Registry ®istry, Logger &logger, -	              Manager &manager, Handle<model::Project> project) -	    : scope(scope), -	      registry(registry), -	      logger(logger), -	      manager(manager), -	      project(project){}; -}; - -/** - * Abstract parser class. This class builds the basic interface that should be - * used by any parser which reads data from an input stream and transforms it - * into an Ousía node graph. - */ -class Parser { -public: -	Parser(){};  	Parser(const Parser &) = delete;  	/** -	 * Returns a set containing all mime types supported by the parser. The mime -	 * types are used to describe the type of the document that is read by the -	 * parser. The default implementation returns an empty set. This method -	 * should be overridden by derived classes. -	 * -	 * @return a set containing the string value of the supported mime types. +	 * Virtual destructor.  	 */ -	virtual std::set<std::string> mimetypes() -	{ -		return std::set<std::string>{}; -	}; +	virtual ~Parser(){};  	/**  	 * Parses the given input stream and returns a corresponding node for @@ -132,9 +90,9 @@ public:  	 * @return a reference to the node representing the subgraph that has been  	 * created. The resulting node may point at not yet resolved entities, the  	 * calling code will try to resolve these. If no valid node can be produced, -	 * a corresponding LoggableException must be thrown by the parser. +	 * a corresponding ParserException must be thrown by the parser.  	 */ -	virtual Rooted<Node> parse(CharReader &reader, ParserContext &ctx) = 0; +	Rooted<Node> parse(CharReader &reader, ParserContext &ctx);  	/**  	 * Parses the given string and returns a corresponding node for @@ -152,7 +110,6 @@ public:  	Rooted<Node> parse(const std::string &str, ParserContext &ctx);  };  } -}  #endif /* _OUSIA_PARSER_HPP_ */ diff --git a/src/core/parser/ParserContext.cpp b/src/core/parser/ParserContext.cpp new file mode 100644 index 0000000..fa26c59 --- /dev/null +++ b/src/core/parser/ParserContext.cpp @@ -0,0 +1,36 @@ +/* +    Ousía +    Copyright (C) 2014, 2015  Benjamin Paaßen, Andreas Stöckel + +    This program is free software: you can redistribute it and/or modify +    it under the terms of the GNU General Public License as published by +    the Free Software Foundation, either version 3 of the License, or +    (at your option) any later version. + +    This program is distributed in the hope that it will be useful, +    but WITHOUT ANY WARRANTY; without even the implied warranty of +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +    GNU General Public License for more details. + +    You should have received a copy of the GNU General Public License +    along with this program.  If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "ParserContext.hpp" + +namespace ousia { + +/* Class ParserContext */ + +ParserContext::ParserContext(ParserScope &scope, Registry ®istry, +                             Logger &logger, Manager &manager, +                             Handle<model::Project> project) +    : scope(scope), +      registry(registry), +      logger(logger), +      manager(manager), +      project(project) +{ +} +} + diff --git a/src/core/parser/ParserContext.hpp b/src/core/parser/ParserContext.hpp new file mode 100644 index 0000000..bb64600 --- /dev/null +++ b/src/core/parser/ParserContext.hpp @@ -0,0 +1,92 @@ +/* +    Ousía +    Copyright (C) 2014, 2015  Benjamin Paaßen, Andreas Stöckel + +    This program is free software: you can redistribute it and/or modify +    it under the terms of the GNU General Public License as published by +    the Free Software Foundation, either version 3 of the License, or +    (at your option) any later version. + +    This program is distributed in the hope that it will be useful, +    but WITHOUT ANY WARRANTY; without even the implied warranty of +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +    GNU General Public License for more details. + +    You should have received a copy of the GNU General Public License +    along with this program.  If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file ParserContext.hpp + * + * Contains the ParserContext, a struct containing references to all the + * important structures a Parser needs to access while parsing an input stream. + * + * @author Andreas Stöckel (astoecke@techfak.uni-bielefeld.de) + */ + +#ifndef _OUSIA_PARSER_CONTEXT_HPP_ +#define _OUSIA_PARSER_CONTEXT_HPP_ + +#include <core/managed/Managed.hpp> +#include <core/model/Node.hpp> +#include <core/model/Project.hpp> + +namespace ousia { + +// Forward declaration +class Logger; +class ParserScope; +class Registry; + +/** + * Struct containing the objects that are passed to a parser instance. + */ +struct ParserContext { +	/** +	 * Reference to the ParserScope instance that should be used within the parser. +	 */ +	ParserScope &scope; + +	/** +	 * Reference to the Registry instance that should be used within the parser. +	 */ +	Registry ®istry; + +	/** +	 * Reference to the Logger the parser should log any messages to. +	 */ +	Logger &logger; + +	/** +	 * Reference to the Manager the parser should append nodes to. +	 */ +	Manager &manager; + +	/** +	 * Project instance into which the new content should be parsed. +	 */ +	Rooted<model::Project> project; + +	/** +	 * Constructor of the ParserContext class. +	 * +	 * @param scope is a reference to the ParserScope instance that should be +	 * used to lookup names. +	 * @param registry is a reference at the Registry class, which allows to +	 * obtain references at parsers for other formats or script engine +	 * implementations. +	 * @param logger is a reference to the Logger instance that should be used +	 * to log error messages and warnings that occur while parsing the document. +	 * @param manager is a Reference to the Manager the parser should append +	 * nodes to. +	 * @param project is the project into which the content should be parsed. +	 */ +	ParserContext(ParserScope &scope, Registry ®istry, Logger &logger, +	              Manager &manager, Handle<model::Project> project); +}; + +} + +#endif /* _OUSIA_PARSER_CONTEXT_HPP_ */ + diff --git a/src/core/parser/Scope.cpp b/src/core/parser/ParserScope.cpp index 01292df..b236a1f 100644 --- a/src/core/parser/Scope.cpp +++ b/src/core/parser/ParserScope.cpp @@ -18,35 +18,14 @@  #include <core/common/Utils.hpp> -#include "Scope.hpp" +#include "ParserScope.hpp"  namespace ousia { -namespace parser { -/* Class GuardedScope */ +/* Class ParserScopeBase */ -GuardedScope::GuardedScope(Scope *scope, Handle<Node> node) : scope(scope) -{ -	scope->push(node); -} - -GuardedScope::~GuardedScope() -{ -	if (scope) { -		scope->pop(); -	} -} - -GuardedScope::GuardedScope(GuardedScope &&s) -{ -	scope = s.scope; -	s.scope = nullptr; -} - -/* Class ScopeBase */ - -Rooted<Node> ScopeBase::resolve(const std::vector<std::string> &path, -                                const Rtti &type, Logger &logger) +Rooted<Node> ParserScopeBase::resolve(const std::vector<std::string> &path, +                                      const Rtti &type, Logger &logger)  {  	// Go up the stack and try to resolve the  	for (auto it = nodes.rbegin(); it != nodes.rend(); it++) { @@ -101,22 +80,17 @@ bool DeferredResolution::resolve(Logger &logger)  	return false;  } -/* Class Scope */ - -void Scope::push(Handle<Node> node) { nodes.push_back(node); } +/* Class ParserScope */ -void Scope::pop() { nodes.pop_back(); } +void ParserScope::push(Handle<Node> node) { nodes.push_back(node); } -GuardedScope Scope::descend(Handle<Node> node) -{ -	return GuardedScope{this, node}; -} +void ParserScope::pop() { nodes.pop_back(); } -Rooted<Node> Scope::getRoot() const { return nodes.front(); } +Rooted<Node> ParserScope::getRoot() const { return nodes.front(); } -Rooted<Node> Scope::getLeaf() { return nodes.back(); } +Rooted<Node> ParserScope::getLeaf() { return nodes.back(); } -bool Scope::resolve(const std::vector<std::string> &path, const Rtti &type, +bool ParserScope::resolve(const std::vector<std::string> &path, const Rtti &type,                      Logger &logger, ResolutionImposterCallback imposterCallback,                      ResolutionResultCallback resultCallback,                      const SourceLocation &location) @@ -128,11 +102,11 @@ bool Scope::resolve(const std::vector<std::string> &path, const Rtti &type,  	return true;  } -bool Scope::resolve(const std::vector<std::string> &path, const Rtti &type, +bool ParserScope::resolve(const std::vector<std::string> &path, const Rtti &type,                      Logger &logger, ResolutionResultCallback resultCallback,                      const SourceLocation &location)  { -	Rooted<Node> res = ScopeBase::resolve(path, type, logger); +	Rooted<Node> res = ParserScopeBase::resolve(path, type, logger);  	if (res != nullptr) {  		try {  			resultCallback(res, logger); @@ -146,7 +120,7 @@ bool Scope::resolve(const std::vector<std::string> &path, const Rtti &type,  	return false;  } -bool Scope::performDeferredResolution(Logger &logger) +bool ParserScope::performDeferredResolution(Logger &logger)  {  	// Repeat the resolution process as long as something has changed in the  	// last iteration (resolving a node may cause other nodes to be resolvable). @@ -176,8 +150,8 @@ bool Scope::performDeferredResolution(Logger &logger)  	// Output error messages for all elements for which resolution did not  	// succeed.  	for (const auto &failed : deferred) { -		logger.error(std::string("Could not resolve ")  + failed.type.name + std::string(" \"") + -		                 Utils::join(failed.path, ".") + +		logger.error(std::string("Could not resolve ") + failed.type.name + +		                 std::string(" \"") + Utils::join(failed.path, ".") +  		                 std::string("\""),  		             failed.location);  	} @@ -185,4 +159,4 @@ bool Scope::performDeferredResolution(Logger &logger)  	return false;  }  } -} + diff --git a/src/core/parser/Scope.hpp b/src/core/parser/ParserScope.hpp index b9b7f80..a759738 100644 --- a/src/core/parser/Scope.hpp +++ b/src/core/parser/ParserScope.hpp @@ -16,8 +16,8 @@      along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -#ifndef _OUSIA_PARSER_SCOPE_H_ -#define _OUSIA_PARSER_SCOPE_H_ +#ifndef _OUSIA_PARSER_SCOPE_HPP_ +#define _OUSIA_PARSER_SCOPE_HPP_  #include <functional>  #include <list> @@ -29,19 +29,21 @@  #include <core/model/Node.hpp>  /** - * @file Scope.hpp + * @file ParserScope.hpp   * - * Contains the Scope class used for resolving references based on the current + * Contains the ParserScope class used for resolving references based on the current   * parser state.   *   * @author Andreas Stöckel (astoecke@techfak.uni-bielefeld.de)   */  namespace ousia { -namespace parser {  // Forward declaration -class Scope; +class CharReader; +class Registry; +class Logger; +class ParserScope;  /**   * Callback function type used for creating a dummy object while no correct @@ -57,57 +59,9 @@ using ResolutionResultCallback =      std::function<void(Handle<Node>, Logger &logger)>;  /** - * The GuardedScope class takes care of pushing a Node instance into the - * name resolution stack of a Scope instance and poping this node once the - * ScopedScope instance is deletes. This way you cannot forget to pop a Node - * from a Scope instance as this operation is performed automatically. - */ -class GuardedScope { -private: -	/** -	 * Reference at the backing scope instance. -	 */ -	Scope *scope; - -public: -	/** -	 * Creates a new ScopedScope instance. -	 * -	 * @param scope is the backing Scope instance. -	 * @param node is the Node instance that should be pushed onto the stack of -	 * the Scope instance. -	 */ -	GuardedScope(Scope *scope, Handle<Node> node); - -	/** -	 * Pops the Node given in the constructor form the stack of the Scope -	 * instance. -	 */ -	~GuardedScope(); - -	/** -	 * Move constructor of the ScopedScope class. -	 */ -	GuardedScope(GuardedScope &&); - -	// No copy construction -	GuardedScope(const GuardedScope &) = delete; - -	/** -	 * Provides access at the underlying Scope instance. -	 */ -	Scope *operator->() { return scope; } - -	/** -	 * Provides access at the underlying Scope instance. -	 */ -	Scope &operator*() { return *scope; } -}; - -/**   * Base class for the   */ -class ScopeBase { +class ParserScopeBase {  protected:  	/**  	 * List containing all nodes currently on the scope, with the newest nodes @@ -117,18 +71,18 @@ protected:  public:  	/** -	 * Default constructor, creates an empty Scope instance. +	 * Default constructor, creates an empty ParserScope instance.  	 */ -	ScopeBase() {} +	ParserScopeBase() {}  	/** -	 * Creates a new instance of the ScopeBase class, copying the the given +	 * Creates a new instance of the ParserScopeBase class, copying the the given  	 * nodes as initial start value of the node stack. This could for example  	 * be initialized with the path of a node.  	 *  	 * @param nodes is a node vector containing the current node stack.  	 */ -	ScopeBase(const NodeVector<Node> &nodes) : nodes(nodes) {} +	ParserScopeBase(const NodeVector<Node> &nodes) : nodes(nodes) {}  	/**  	 * Tries to resolve a node for the given type and path for all nodes that @@ -157,7 +111,7 @@ private:  	/**  	 * Copy of the scope at the time when the resolution was first triggered.  	 */ -	ScopeBase scope; +	ParserScopeBase scope;  	/**  	 * Callback function to be called when an element is successfully resolved. @@ -185,7 +139,7 @@ public:  	 * arguments.  	 *  	 * @param nodes is a reference at the current internal node stack of the -	 * Scope class. +	 * ParserScope class.  	 * @param path is the path that was queried when the resolution failed the  	 * first time.  	 * @param type is the Rtti of the element that should be queried. @@ -213,12 +167,11 @@ public:  /**   * Provides an interface for document parsers to resolve references based on the - * current position in the created document tree. The Scope class itself is - * represented as a chain of Scope objects where each element has a reference to - * a Node object attached to it. The descend method can be used to add a new - * scope element to the chain. + * current position in the created document tree. The ParserScope class itself + * is represented as a chain of ParserScope objects where each element has a + * reference to a Node object attached to it.   */ -class Scope : public ScopeBase { +class ParserScope : public ParserScopeBase {  private:  	/**  	 * List containing all deferred resolution descriptors. @@ -227,10 +180,10 @@ private:  public:  	/** -	 * Default constructor of the Scope class, creates an empty Scope with no +	 * Default constructor of the ParserScope class, creates an empty ParserScope with no  	 * element on the internal stack.  	 */ -	Scope() {} +	ParserScope() {}  	/**  	 * Pushes a new node onto the scope. @@ -245,20 +198,14 @@ public:  	void pop();  	/** -	 * Returns a ScopedScope instance, which automatically pushes the given node -	 * into the Scope stack and pops it once the ScopedScope is destroyed. -	 */ -	GuardedScope descend(Handle<Node> node); - -	/** -	 * Returns the top-most Node instance in the Scope hirarchy. +	 * Returns the top-most Node instance in the ParserScope hirarchy.  	 *  	 * @return a reference at the root node.  	 */  	Rooted<Node> getRoot() const;  	/** -	 * Returns the bottom-most Node instance in the Scope hirarchy, e.g. the +	 * Returns the bottom-most Node instance in the ParserScope hirarchy, e.g. the  	 * node that was pushed last onto the stack.  	 *  	 * @return a reference at the leaf node. @@ -475,7 +422,6 @@ public:  	bool performDeferredResolution(Logger &logger);  };  } -} -#endif /* _OUSIA_PARSER_SCOPE_H_ */ +#endif /* _OUSIA_PARSER_SCOPE_HPP_ */ diff --git a/src/core/parser/ParserStack.cpp b/src/core/parser/ParserStack.cpp index 9cf782f..3792ee8 100644 --- a/src/core/parser/ParserStack.cpp +++ b/src/core/parser/ParserStack.cpp @@ -24,7 +24,6 @@  #include <core/common/Exceptions.hpp>  namespace ousia { -namespace parser {  /* A default handler */ @@ -186,5 +185,4 @@ void ParserStack::data(const std::string &data, int field)  	stack.top().handler->data(data, field);  }  } -} diff --git a/src/core/parser/ParserStack.hpp b/src/core/parser/ParserStack.hpp index 492ab9c..6296dff 100644 --- a/src/core/parser/ParserStack.hpp +++ b/src/core/parser/ParserStack.hpp @@ -42,9 +42,9 @@  #include <core/common/Argument.hpp>  #include "Parser.hpp" +#include "ParserContext.hpp"  namespace ousia { -namespace parser {  /**   * The State type alias is used to @@ -139,7 +139,7 @@ public:  	const std::string &name() { return handlerData.name; } -	Scope &scope() { return handlerData.ctx.scope; } +	ParserScope &scope() { return handlerData.ctx.scope; }  	Registry ®istry() { return handlerData.ctx.registry; } @@ -322,6 +322,11 @@ private:  	std::stack<HandlerInstance> stack;  	/** +	 * Reference at some user defined data. +	 */ +	void *userData; + +	/**  	 * Used internally to get all expected command names for the given state  	 * (does not work if the current Handler instance allows arbitrary  	 * children). This function is used to build error messages. @@ -340,8 +345,9 @@ public:  	 * corresponding HandlerDescriptor instances.  	 */  	ParserStack(ParserContext &ctx, -	            const std::multimap<std::string, HandlerDescriptor> &handlers) -	    : ctx(ctx), handlers(handlers){}; +	            const std::multimap<std::string, HandlerDescriptor> &handlers, +	            void *userData = nullptr) +	    : ctx(ctx), handlers(handlers), userData(userData){};  	/**  	 * Returns the state the ParserStack instance currently is in. @@ -419,9 +425,15 @@ public:  	 * @return a reference to the parser context.  	 */  	ParserContext &getContext() { return ctx; } + +	/** +	 * Returns the user defined data. +	 * +	 * @return the userData pointer that was given in the constructor. +	 */ +	void *getUserData() { return userData; }  };  } -}  #endif /* _OUSIA_PARSER_STACK_HPP_ */ diff --git a/src/core/resource/ResourceManager.cpp b/src/core/resource/ResourceManager.cpp new file mode 100644 index 0000000..f154c9c --- /dev/null +++ b/src/core/resource/ResourceManager.cpp @@ -0,0 +1,275 @@ +/* +    Ousía +    Copyright (C) 2014, 2015  Benjamin Paaßen, Andreas Stöckel + +    This program is free software: you can redistribute it and/or modify +    it under the terms of the GNU General Public License as published by +    the Free Software Foundation, either version 3 of the License, or +    (at your option) any later version. + +    This program is distributed in the hope that it will be useful, +    but WITHOUT ANY WARRANTY; without even the implied warranty of +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +    GNU General Public License for more details. + +    You should have received a copy of the GNU General Public License +    along with this program.  If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <vector> + +#include <core/common/CharReader.hpp> +#include <core/common/Exceptions.hpp> +#include <core/common/Logger.hpp> +#include <core/common/Rtti.hpp> +#include <core/common/Utils.hpp> +#include <core/model/Node.hpp> +#include <core/parser/ParserContext.hpp> +#include <core/parser/Parser.hpp> +#include <core/Registry.hpp> + +#include "ResourceManager.hpp" +#include "ResourceUtils.hpp" + +namespace ousia { + +/* Static helper functions */ + +static void logUnsopportedType(Logger &logger, Resource &resource, const RttiSet &supportedTypes) +{ +	// Build a list containing the expected type names +	std::vector<std::string> expected; +	for (const Rtti *supportedType : supportedTypes) { +		expected.push_back(supportedType->name); +	} + +	// Log the actual error message +	logger.error( +	    std::string("Expected the file \"") + resource.getLocation() + +	    std::string("\" to define one of the following internal types ") + +	    Utils::join(expected, ", ", "{", "}")); +} + +/* Class ResourceManager */ + +SourceId ResourceManager::allocateSourceId(const Resource &resource) +{ +	// Increment the source id and make sure the values don't overflow +	SourceId sourceId = nextSourceId++; +	if (sourceId == InvalidSourceId) { +		nextSourceId = InvalidSourceId; +		throw OusiaException{"Internal resource handles depleted!"}; +	} + +	// Register the node and the resource with this id +	locations[resource.getLocation()] = sourceId; +	resources[sourceId] = resource; + +	return sourceId; +} + +void ResourceManager::storeNode(SourceId sourceId, Handle<Node> node) +{ +	nodes[sourceId] = node->getUid(); +} + +void ResourceManager::purgeResource(SourceId sourceId) +{ +	Resource res = getResource(sourceId); +	if (res.isValid()) { +		locations.erase(res.getLocation()); +	} +	resources.erase(sourceId); +	nodes.erase(sourceId); +	lineNumberCache.erase(sourceId); +} + +Rooted<Node> ResourceManager::parse(ParserContext &ctx, Resource &resource, +                                    const std::string &mimetype, +                                    const RttiSet &supportedTypes) +{ +	// Try to deduce the mimetype of no mimetype was given +	std::string mime = mimetype; +	if (mime.empty()) { +		mime = ctx.registry.getMimetypeForFilename(resource.getLocation()); +		if (mime.empty()) { +			ctx.logger.error(std::string("Filename \"") + resource.getLocation() + +			                 std::string( +			                     "\" has an unknown file extension. Explicitly " +			                     "specify a mimetype.")); +			return nullptr; +		} +	} + +	// Fetch a parser for the mimetype +	const std::pair<Parser *, RttiSet> &parserDescr = +	    ctx.registry.getParserForMimetype(mime); +	Parser *parser = parserDescr.first; + +	// Make sure a parser was found +	if (!parser) { +		ctx.logger.error(std::string("Cannot parse files of type \"") + mime + +		                 std::string("\"")); +		return nullptr; +	} + +	// Make sure the parser returns at least one of the supported types +	if (!Rtti::setIsOneOf(parserDescr.second, supportedTypes)) { +		logUnsopportedType(ctx.logger, resource, supportedTypes); +		return nullptr; +	} + +	// Allocate a new SourceId handle for this Resource +	SourceId sourceId = allocateSourceId(resource); + +	// We can now try to parse the given file +	Rooted<Node> node; +	try { +		// Set the current source id in the logger instance +		ScopedLogger logger(ctx.logger, SourceLocation{sourceId}); + +		// Fetch the input stream and create a char reader +		std::unique_ptr<std::istream> is = resource.stream(); +		CharReader reader(*is, sourceId); + +		// Actually parse the input stream +		node = parser->parse(reader, ctx); +		if (node == nullptr) { +			throw LoggableException{"Internal error: Parser returned null."}; +		} +	} catch (LoggableException ex) { +		// Remove all data associated with the allocated source id +		purgeResource(sourceId); + +		// Log the exception and return nullptr +		ctx.logger.log(ex); +		return nullptr; +	} + +	// Store the parsed node along with the sourceId +	storeNode(sourceId, node); + +	// Return the parsed node +	return node; +} + +SourceId ResourceManager::getSourceId(const std::string &location) +{ +	auto it = locations.find(location); +	if (it != locations.end()) { +		return it->second; +	} +	return InvalidSourceId; +} + +SourceId ResourceManager::getSourceId(const Resource &resource) +{ +	if (resource.isValid()) { +		return getSourceId(resource.getLocation()); +	} +	return InvalidSourceId; +} + +const Resource &ResourceManager::getResource(SourceId sourceId) const +{ +	auto it = resources.find(sourceId); +	if (it != resources.end()) { +		return it->second; +	} +	return NullResource; +} + +Rooted<Node> ResourceManager::getNode(Manager &mgr, SourceId sourceId) +{ +	auto it = nodes.find(sourceId); +	if (it != nodes.end()) { +		Managed *managed = mgr.getManaged(sourceId); +		if (managed != nullptr) { +			return dynamic_cast<Node *>(managed); +		} else { +			purgeResource(sourceId); +		} +	} +	return nullptr; +} + +Rooted<Node> ResourceManager::getNode(Manager &mgr, const std::string &location) +{ +	return getNode(mgr, getSourceId(location)); +} + +Rooted<Node> ResourceManager::getNode(Manager &mgr, const Resource &resource) +{ +	return getNode(mgr, getSourceId(resource)); +} + +Rooted<Node> ResourceManager::link(ParserContext &ctx, const std::string &path, +                                   const std::string &mimetype, +                                   const std::string &rel, +                                   const RttiSet &supportedTypes, +                                   const Resource &relativeTo) +{ +	// Try to deduce the ResourceType +	ResourceType resourceType = +	    ResourceUtils::deduceResourceType(rel, supportedTypes, ctx.logger); + +	// Lookup the resource for given path and resource type +	Resource resource; +	if (!ctx.registry.locateResource(resource, path, resourceType, +	                                 relativeTo)) { +		ctx.logger.error("File \"" + path + "\" not found."); +		return nullptr; +	} + +	// Try to shrink the set of supportedTypes +	RttiSet types = ResourceUtils::limitRttiTypes(supportedTypes, rel); + +	// Check whether the resource has already been parsed +	Rooted<Node> node = getNode(ctx.manager, resource); +	if (node == nullptr) { +		// Node has not already been parsed, parse it now +		node = parse(ctx, resource, mimetype, supportedTypes); + +		// Abort if parsing failed +		if (node == nullptr) { +			return nullptr; +		} +	} + +	// Make sure the node has one of the supported types +	if (!node->type().isOneOf(supportedTypes)) { +		logUnsopportedType(ctx.logger, resource, supportedTypes); +		return nullptr; +	} + +	return node; +} + +Rooted<Node> ResourceManager::link(ParserContext &ctx, const std::string &path, +                                   const std::string &mimetype, +                                   const std::string &rel, +                                   const RttiSet &supportedTypes, +                                   SourceId relativeTo) +{ +	// Fetch the resource corresponding to the source id, make sure it is valid +	const Resource &relativeResource = getResource(relativeTo); +	if (!relativeResource.isValid()) { +		ctx.logger.fatalError("Internal error: Invalid SourceId supplied."); +		return nullptr; +	} + +	// Continue with the usual include routine +	return link(ctx, path, mimetype, rel, supportedTypes, relativeResource); +} + +SourceContext ResourceManager::buildContext(const SourceLocation &location) +{ +	SourceContext res; + +	// TODO + +	return res; +} + +} + diff --git a/src/core/resource/ResourceManager.hpp b/src/core/resource/ResourceManager.hpp new file mode 100644 index 0000000..51c00e3 --- /dev/null +++ b/src/core/resource/ResourceManager.hpp @@ -0,0 +1,236 @@ +/* +    Ousía +    Copyright (C) 2014, 2015  Benjamin Paaßen, Andreas Stöckel + +    This program is free software: you can redistribute it and/or modify +    it under the terms of the GNU General Public License as published by +    the Free Software Foundation, either version 3 of the License, or +    (at your option) any later version. + +    This program is distributed in the hope that it will be useful, +    but WITHOUT ANY WARRANTY; without even the implied warranty of +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +    GNU General Public License for more details. + +    You should have received a copy of the GNU General Public License +    along with this program.  If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file ResourceManager.hpp + * + * Defines the ResourceManager class which is responsible for keeping track of + * already included resources and to retrieve CharReader instance for not-yet + * parsed resources. + * + * @author Andreas Stöckel (astoecke@techfak.uni-bielefeld.de) + */ + +#ifndef _OUSIA_RESOURCE_MANAGER_HPP_ +#define _OUSIA_RESOURCE_MANAGER_HPP_ + +#include <string> +#include <unordered_map> + +#include <core/common/Location.hpp> +#include <core/common/Rtti.hpp> +#include <core/managed/Managed.hpp> + +#include "Resource.hpp" + +namespace ousia { + +// Forward declarations +class Node; +class ParserContext; +extern const Resource NullResource; + +/** + * The ResourceManager class is responsible for keepking track of all included + * resources. It retrieves CharReader instances for not-yet parsed resources + * and returns references for those resources that already have been parsed. + */ +class ResourceManager { +private: +	/** +	 * Next SourceId to be used. +	 */ +	SourceId nextSourceId = 0; + +	/** +	 * Map between Resource locations and the corresponding SourceId. +	 */ +	std::unordered_map<std::string, SourceId> locations; + +	/** +	 * Map used for mapping SourceId instances to the underlying resource. +	 */ +	std::unordered_map<SourceId, Resource> resources; + +	/** +	 * Map between a SourceId and the corresponding (if available) parsed node +	 * uid (this resembles weak references to the Node instance). +	 */ +	std::unordered_map<SourceId, ManagedUid> nodes; + +	/** +	 * Cache used for translating byte offsets to line numbers. Maps from a +	 * SourceId onto a list of (sorted) SourceOffsets. The index in the list +	 * corresponds to the line number. +	 */ +	std::unordered_map<SourceId, std::vector<SourceOffset>> lineNumberCache; + +	/** +	 * Allocates a new SourceId for the given resource. +	 * +	 * @param resource is the Resource that should be associated with the newly +	 * allocated SourceId. +	 * @return a new SourceId describing the given resource. +	 */ +	SourceId allocateSourceId(const Resource &resource); + +	/** +	 * Registers the parsed node for this node id. +	 * +	 * @param sourceId is SourceId instance of the resource. +	 * @param node is the node that was parsed from that resource. +	 */ +	void storeNode(SourceId sourceId, Handle<Node> node); + +	/** +	 * Removes a resource from the internal stores. +	 * +	 * @param sourceId is the id of the file that should be removed. +	 */ +	void purgeResource(SourceId sourceId); + +	/** +	 * Used internally to parse the given resource. +	 * +	 * @param ctx is the context from the Registry and the Logger instance will +	 * be looked up. +	 * @param resource is the resource from which the input stream should be +	 * obtained. +	 * @param mimetype is the mimetype of the resource that should be parsed +	 * (may be empty, in which case the mimetype is deduced from the file +	 * extension) +	 * @param supportedTypes contains the types of the returned Node the caller +	 * can deal with. Note that only the types the parser claims to return are +	 * checked, not the actual result. +	 * @return the parsed node or nullptr if something goes wrong. +	 */ +	Rooted<Node> parse(ParserContext &ctx, Resource &resource, +	                   const std::string &mimetype, +	                   const RttiSet &supportedTypes); + +public: +	/** +	 * Returns the sourceId for the given location string. +	 * +	 * @param location is the location string for which the resource id should +	 * be returned. +	 * @return the SourceId that can be used to identify the Resource, or +	 * InvalidSourceId if the specified location is not loaded. +	 */ +	SourceId getSourceId(const std::string &location); + +	/** +	 * Returns the sourceId for the given Resource. +	 * +	 * @param resource is the Resource for which the sourceId should be +	 * returned. +	 * @return the SourceId that can be used to identify the Resource, or +	 * InvalidSourceId if the specified resource is not loaded or invalid. +	 */ +	SourceId getSourceId(const Resource &resource); + +	/** +	 * Returns a Resource instance for the given SourceId. +	 * +	 * @param sourceId is the id of the Resource instance that should be +	 * returned. +	 * @return the Resource instance corresponding to the given sourceId. If the +	 * sourceId is invalid, the returned Resource will be invalid (a reference +	 * at NullResource). +	 */ +	const Resource &getResource(SourceId sourceId) const; + +	/** +	 * Returns the node that is associated with the given SourceId or nullptr if +	 * the Node no longer exists or the supplied SourceId is invalid. +	 * +	 * @param mgr is the Manager instance that should be used to resolve the +	 * internal weak reference to the Node instance. +	 * @param sourceId is the id of the resource for which the parsed Node +	 * instance should be returned. +	 * @return the Node instance corresponding to the given sourceId. +	 */ +	Rooted<Node> getNode(Manager &mgr, SourceId sourceId); + +	/** +	 * Returns the node that is associated with the given location or nullptr if +	 * the Node no longer exists or the supplied location was never parsed. +	 * +	 * @param mgr is the Manager instance that should be used to resolve the +	 * internal weak reference to the Node instance. +	 * @param location is the location from which the node was parsed. +	 * @return the Node instance corresponding to the given location. +	 */ +	Rooted<Node> getNode(Manager &mgr, const std::string &location); + +	/** +	 * Returns the node that is associated with the given resource or nullptr if +	 * the Node no longer exists or the supplied resource was never parsed. +	 * +	 * @param mgr is the Manager instance that should be used to resolve the +	 * internal weak reference to the Node instance. +	 * @param resource is the resource from which the node was parsed. +	 * @return the Node instance corresponding to the given resource. +	 */ +	Rooted<Node> getNode(Manager &mgr, const Resource &resource); + +	/** +	 * Resolves the reference to the file specified by the given path and -- if +	 * this has not already happened -- parses the file. Logs any problem in +	 * the logger instance of the given ParserContext. +	 * +	 * @param ctx is the context from the Registry and the Logger instance will +	 * be looked up. +	 * @param path is the path to the file that should be included. +	 * @param mimetype is the mimetype the file was included with. If no +	 * mimetype is given, the path must have an extension that is known by +	 */ +	Rooted<Node> link(ParserContext &ctx, const std::string &path, +	                  const std::string &mimetype = "", +	                  const std::string &rel = "", +	                  const RttiSet &supportedTypes = RttiSet{}, +	                  const Resource &relativeTo = NullResource); + +	/** +	 * Resolves the reference to the file specified by the given path and -- if +	 * this has not already happened -- parses the file. Logs any problem in +	 * the logger instance of the given ParserContext. +	 */ +	Rooted<Node> link(ParserContext &ctx, const std::string &path, +	                  const std::string &mimetype, const std::string &rel, +	                  const RttiSet &supportedTypes, SourceId relativeTo); + +	/** +	 * Creates and returns a SourceContext structure containing information +	 * about the given SourceLocation (such as line and column number). Throws +	 * a LoggableException if an irrecoverable error occurs while looking up the +	 * context (such as a no longer existing resource). +	 * +	 * @param location is the SourceLocation for which context information +	 * should be retrieved. This method is used by the Logger class to print +	 * pretty messages. +	 * @return a valid SourceContext if a valid SourceLocation was given or an +	 * invalid SourceContext if the location is invalid. +	 */ +	SourceContext buildContext(const SourceLocation &location); + +}; +} + +#endif /* _OUSIA_RESOURCE_MANAGER_HPP_ */ + diff --git a/src/core/resource/ResourceUtils.cpp b/src/core/resource/ResourceUtils.cpp new file mode 100644 index 0000000..7c42716 --- /dev/null +++ b/src/core/resource/ResourceUtils.cpp @@ -0,0 +1,138 @@ +/* +    Ousía +    Copyright (C) 2014, 2015  Benjamin Paaßen, Andreas Stöckel + +    This program is free software: you can redistribute it and/or modify +    it under the terms of the GNU General Public License as published by +    the Free Software Foundation, either version 3 of the License, or +    (at your option) any later version. + +    This program is distributed in the hope that it will be useful, +    but WITHOUT ANY WARRANTY; without even the implied warranty of +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +    GNU General Public License for more details. + +    You should have received a copy of the GNU General Public License +    along with this program.  If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <core/common/Logger.hpp> +#include <core/common/Rtti.hpp> +#include <core/common/Utils.hpp> + +#include "ResourceUtils.hpp" + +namespace ousia { + +namespace RttiTypes { +extern const Rtti Document; +extern const Rtti Domain; +extern const Rtti Node; +extern const Rtti Typesystem; +} + +/** + * Map mapping from relations (the "rel" attribute in includes) to the + * corresponding ResourceType. + */ +static const std::unordered_map<std::string, ResourceType> RelResourceTypeMap{ +    {"document", ResourceType::DOCUMENT}, +    {"domain", ResourceType::DOMAIN_DESC}, +    {"typesystem", ResourceType::TYPESYSTEM}}; + +/** + * Map mapping from relations to the corresponding Rtti descriptor. + */ +static const std::unordered_map<std::string, const Rtti *> RelRttiTypeMap{ +    {"document", &RttiTypes::Document}, +    {"domain", &RttiTypes::Domain}, +    {"typesystem", &RttiTypes::Typesystem}}; + +/** + * Map mapping from Rtti pointers to the corresponding ResourceType. + */ +static const std::unordered_map<const Rtti *, ResourceType> RttiResourceTypeMap{ +    {&RttiTypes::Document, ResourceType::DOCUMENT}, +    {&RttiTypes::Domain, ResourceType::DOMAIN_DESC}, +    {&RttiTypes::Typesystem, ResourceType::TYPESYSTEM}}; + +ResourceType ResourceUtils::deduceResourceType(const std::string &rel, +                                               const RttiSet &supportedTypes, +                                               Logger &logger) +{ +	ResourceType res; + +	// Try to deduce the ResourceType from the "rel" attribute +	res = deduceResourceType(rel, logger); + +	// If this did not work, try to deduce the ResourceType from the +	// supportedTypes supplied by the Parser instance. +	if (res == ResourceType::UNKNOWN) { +		res = deduceResourceType(supportedTypes, logger); +	} +	if (res == ResourceType::UNKNOWN) { +		logger.note( +		    "Ambigous resource type, consider specifying the \"rel\" " +		    "attribute"); +	} +	return res; +} + +ResourceType ResourceUtils::deduceResourceType(const std::string &rel, +                                               Logger &logger) +{ +	std::string s = Utils::toLower(rel); +	if (!s.empty()) { +		auto it = RelResourceTypeMap.find(s); +		if (it != RelResourceTypeMap.end()) { +			return it->second; +		} else { +			logger.error(std::string("Unknown relation \"") + rel + +			             std::string("\"")); +		} +	} +	return ResourceType::UNKNOWN; +} + +ResourceType ResourceUtils::deduceResourceType(const RttiSet &supportedTypes, +                                               Logger &logger) +{ +	if (supportedTypes.size() == 1U) { +		auto it = RttiResourceTypeMap.find(*supportedTypes.begin()); +		if (it != RttiResourceTypeMap.end()) { +			return it->second; +		} +	} +	return ResourceType::UNKNOWN; +} + +const Rtti *ResourceUtils::deduceRttiType(const std::string &rel) +{ +	std::string s = Utils::toLower(rel); +	if (!s.empty()) { +		auto it = RelRttiTypeMap.find(s); +		if (it != RelRttiTypeMap.end()) { +			return it->second; +		} +	} +	return &RttiTypes::Node; +} + +RttiSet ResourceUtils::limitRttiTypes(const RttiSet &supportedTypes, +                                      const std::string &rel) +{ +	return limitRttiTypes(supportedTypes, deduceRttiType(rel)); +} + +RttiSet ResourceUtils::limitRttiTypes(const RttiSet &supportedTypes, +                                      const Rtti *type) +{ +	RttiSet res; +	for (const Rtti *supportedType : supportedTypes) { +		if (supportedType->isa(*type)) { +			res.insert(supportedType); +		} +	} +	return res; +} +} diff --git a/src/core/resource/ResourceUtils.hpp b/src/core/resource/ResourceUtils.hpp new file mode 100644 index 0000000..13f9251 --- /dev/null +++ b/src/core/resource/ResourceUtils.hpp @@ -0,0 +1,128 @@ +/* +    Ousía +    Copyright (C) 2014, 2015  Benjamin Paaßen, Andreas Stöckel + +    This program is free software: you can redistribute it and/or modify +    it under the terms of the GNU General Public License as published by +    the Free Software Foundation, either version 3 of the License, or +    (at your option) any later version. + +    This program is distributed in the hope that it will be useful, +    but WITHOUT ANY WARRANTY; without even the implied warranty of +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +    GNU General Public License for more details. + +    You should have received a copy of the GNU General Public License +    along with this program.  If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file ResourceUtils.hpp + * + * Contains the ResourceUtils class which defines a set of static utility + * functions for dealing with Resources and ResourceTypes. + * + * @author Andreas Stöckel (astoecke@techfak.uni-bielefeld.de) + */ + +#ifndef _OUSIA_RESOURCE_UTILS_HPP_ +#define _OUSIA_RESOURCE_UTILS_HPP_ + +#include <string> + +#include <core/common/Rtti.hpp> + +#include "Resource.hpp" + +namespace ousia { + +/** + * Class containing static utility functions for dealing with Resources and + * ResourceTypes. + */ +class ResourceUtils { +public: +	/** +	 * Function used to deduce the resource type from a given "relation" string +	 * and a set of RTTI types into which the resource should be converted by a +	 * parser. +	 * +	 * @param rel is a relation string which specifies the type of the resource. +	 * May be empty. +	 * @param supportedTypes is a set of RTTI types into which the resource +	 * should be converted by a parser. Set may be empty. +	 * @param logger is the Logger instance to which errors should be logged. +	 * @return a ResourceType specifier. +	 */ +	static ResourceType deduceResourceType(const std::string &rel, +	                                       const RttiSet &supportedTypes, +	                                       Logger &logger); + +	/** +	 * Function used to deduce the resource type from a given "relation" string. +	 * +	 * @param rel is a relation string which specifies the type of the resource. +	 * May be empty. +	 * @param logger is the Logger instance to which errors should be logged +	 * (e.g. if the relation string is invalid). +	 * @return a ResourceType specifier. +	 */ +	static ResourceType deduceResourceType(const std::string &rel, +	                                       Logger &logger); + +	/** +	 * Function used to deduce the resource type from a set of RTTI types into +	 * which the resource should be converted by a parser. +	 * +	 * @param supportedTypes is a set of RTTI types into which the resource +	 * should be converted by a parser. Set may be empty. +	 * @param logger is the Logger instance to which errors should be logged. +	 * @return a ResourceType specifier. +	 */ +	static ResourceType deduceResourceType(const RttiSet &supportedTypes, +	                                       Logger &logger); + +	/** +	 * Transforms the given relation string to the corresponding RttiType. +	 * +	 * @param rel is a relation string which specifies the type of the resource. +	 * May be empty. +	 * @return a pointer at the corresponding Rtti instance or a pointer at the +	 * Rtti descriptor of the Node class (the most general Node type) if the +	 * given relation type is unknown. +	 */ +	static const Rtti *deduceRttiType(const std::string &rel); + +	/** +	 * Reduces the number of types supported by a parser as the type of a +	 * resource to the intersection of the given supported types and the RTTI +	 * type associated with the given relation string. +	 * +	 * @param supportedTypes is a set of RTTI types into which the resource +	 * should be converted by a parser. Set may be empty. +	 * @param rel is a relation string which specifies the type of the resource. +	 * @return the supported type set limited to those types that can actually +	 * be returned according to the given relation string. +	 */ +	static RttiSet limitRttiTypes(const RttiSet &supportedTypes, +	                              const std::string &rel); + +	/** +	 * Reduces the number of types supported by a parser as the type of a +	 * resource to the intersection of the given supported types and the RTTI +	 * type associated with the given relation string. +	 * +	 * @param supportedTypes is a set of RTTI types into which the resource +	 * should be converted by a parser. Set may be empty. +	 * @param type is the type that is to be expected from the parser. +	 * @return the supported type set limited to those types that can actually +	 * be returned according to the given relation string (form an isa +	 * relationship with the given type). +	 */ +	static RttiSet limitRttiTypes(const RttiSet &supportedTypes, +	                              const Rtti *type); +}; +} + +#endif /* _OUSIA_RESOURCE_UTILS_HPP_ */ + diff --git a/src/plugins/css/CSSParser.cpp b/src/plugins/css/CSSParser.cpp index 40486cc..8cb41ea 100644 --- a/src/plugins/css/CSSParser.cpp +++ b/src/plugins/css/CSSParser.cpp @@ -19,10 +19,9 @@  #include "CSSParser.hpp"  #include <core/common/VariantReader.hpp> +#include <core/parser/ParserContext.hpp>  namespace ousia { -namespace parser { -namespace css {  // CSS code tokens  static const int CURLY_OPEN = 1; @@ -75,7 +74,7 @@ static const std::map<int, CodeTokenDescriptor> CSS_DESCRIPTORS = {      {ESCAPE, {CodeTokenMode::ESCAPE, ESCAPE}},      {LINEBREAK, {CodeTokenMode::LINEBREAK, LINEBREAK}}}; -Rooted<Node> CSSParser::parse(CharReader &reader, ParserContext &ctx) +Rooted<Node> CSSParser::doParse(CharReader &reader, ParserContext &ctx)  {  	CodeTokenizer tokenizer{reader, CSS_ROOT, CSS_DESCRIPTORS};  	tokenizer.ignoreComments = true; @@ -362,5 +361,4 @@ bool CSSParser::expect(int expectedType, CodeTokenizer &tokenizer, Token &t,  	return true;  }  } -} -} + diff --git a/src/plugins/css/CSSParser.hpp b/src/plugins/css/CSSParser.hpp index 1ec54f5..c6594f6 100644 --- a/src/plugins/css/CSSParser.hpp +++ b/src/plugins/css/CSSParser.hpp @@ -24,6 +24,7 @@   *   * @author Benjamin Paassen - bpaassen@techfak.uni-bielefeld.de   */ +  #ifndef _OUSIA_CSS_PARSER_HPP_  #define _OUSIA_CSS_PARSER_HPP_ @@ -36,8 +37,6 @@  #include <core/parser/Parser.hpp>  namespace ousia { -namespace parser { -namespace css {  /**   * This is a context free, recursive parser for a subset of the CSS3 language @@ -139,7 +138,7 @@ private:  	bool expect(int expectedType, CodeTokenizer &tokenizer, Token &t,  	            bool force, ParserContext &ctx); -public: +protected:  	/**  	 * This parses the given input as CSS content as specified by the grammar  	 * seen above. The return value is a Rooted reference to the root of the @@ -157,21 +156,8 @@ public:  	 * @return    returns the root node of the resulting SelectorTree. For more  	 *            information on the return conventions consult the Parser.hpp.  	 */ -	Rooted<Node> parse(CharReader &reader, ParserContext &ctx) override; - -	using Parser::parse; - -	/** -	 * As befits a class called CSSParser, this Parser parses CSS. -	 */ -	std::set<std::string> mimetypes() -	{ -		std::set<std::string> out{"text/css"}; -		return out; -	} +	Rooted<Node> doParse(CharReader &reader, ParserContext &ctx) override;  };  } -} -}  #endif diff --git a/src/plugins/filesystem/FileLocator.cpp b/src/plugins/filesystem/FileLocator.cpp index 467363b..356394e 100644 --- a/src/plugins/filesystem/FileLocator.cpp +++ b/src/plugins/filesystem/FileLocator.cpp @@ -131,7 +131,7 @@ bool FileLocator::doLocate(Resource &resource, const std::string &path,  			base /= path;  			// If we already found a fitting resource there, use that. -			if (fs::exists(base)) { +			if (fs::exists(base) && fs::is_regular_file(base)) {  				std::string location = fs::canonical(base).generic_string();  #ifdef FILELOCATOR_DEBUG_PRINT  				std::cout << "FileLocator: Found \"" << path << "\" at " @@ -143,6 +143,11 @@ bool FileLocator::doLocate(Resource &resource, const std::string &path,  		}  	} +	// If the path starts with "./" or "../" only perform relative lookups! +	if (path.substr(0, 2) == "./" || path.substr(0, 3) == "../") { +		return false; +	} +  	// Otherwise look in the search paths, search backwards, last defined search  	// paths have a higher precedence  	auto it = searchPaths.find(type); @@ -154,7 +159,7 @@ bool FileLocator::doLocate(Resource &resource, const std::string &path,  #endif  			fs::path p{*it};  			p /= path; -			if (fs::exists(p)) { +			if (fs::exists(p) && fs::is_regular_file(p)) {  				std::string location = fs::canonical(p).generic_string();  #ifdef FILELOCATOR_DEBUG_PRINT  				std::cout << "FileLocator: Found \"" << path << "\" in " diff --git a/src/plugins/xml/XmlParser.cpp b/src/plugins/xml/XmlParser.cpp index 434a72c..78d9df8 100644 --- a/src/plugins/xml/XmlParser.cpp +++ b/src/plugins/xml/XmlParser.cpp @@ -25,13 +25,12 @@  #include <core/common/Utils.hpp>  #include <core/common/VariantReader.hpp>  #include <core/parser/ParserStack.hpp> +#include <core/parser/ParserScope.hpp>  #include <core/model/Typesystem.hpp>  #include "XmlParser.hpp"  namespace ousia { -namespace parser { -namespace xml {  using namespace ousia::model; @@ -132,11 +131,12 @@ public:  		    !(defaultValue.isObject() && defaultValue.asObject() == nullptr);  		Rooted<StructType> structType = scope().getLeaf().cast<StructType>(); -		Rooted<Attribute> attribute = structType->createAttribute( -		    name, defaultValue, optional, logger()); +		Rooted<Attribute> attribute = +		    structType->createAttribute(name, defaultValue, optional, logger());  		// Try to resolve the type -		scope().resolve<Type>(type, logger(), +		scope().resolve<Type>( +		    type, logger(),  		    [attribute](Handle<Type> type, Logger &logger) mutable {  			    attribute->setType(type, logger);  			}, @@ -235,16 +235,23 @@ public:  /* Adapter Expat -> ParserStack */ +struct XMLParserUserData { +	SourceId sourceId; +}; +  static SourceLocation syncLoggerPosition(XML_Parser p)  { +	// Fetch the parser stack and the associated user data +	ParserStack *stack = static_cast<ParserStack *>(XML_GetUserData(p)); +	XMLParserUserData *ud = +	    static_cast<XMLParserUserData *>(stack->getUserData()); +  	// Fetch the current location in the XML file -	int line = XML_GetCurrentLineNumber(p); -	int column = XML_GetCurrentColumnNumber(p);  	size_t offs = XML_GetCurrentByteIndex(p); -	SourceLocation loc{line, column, offs}; -	// Update the default location of the current logger instance -	ParserStack *stack = static_cast<ParserStack *>(XML_GetUserData(p)); +	// Build the source location and update the default location of the current +	// logger instance +	SourceLocation loc{ud->sourceId, offs};  	stack->getContext().logger.setDefaultLocation(loc);  	return loc;  } @@ -271,9 +278,9 @@ static void xmlStartElementHandler(void *p, const XML_Char *name,  static void xmlEndElementHandler(void *p, const XML_Char *name)  {  	XML_Parser parser = static_cast<XML_Parser>(p); -	syncLoggerPosition(parser); -  	ParserStack *stack = static_cast<ParserStack *>(XML_GetUserData(parser)); + +	syncLoggerPosition(parser);  	stack->end();  } @@ -291,19 +298,17 @@ static void xmlCharacterDataHandler(void *p, const XML_Char *s, int len)  /* Class XmlParser */ -std::set<std::string> XmlParser::mimetypes() -{ -	return std::set<std::string>{{"text/vnd.ousia.oxm", "text/vnd.ousia.oxd"}}; -} - -Rooted<Node> XmlParser::parse(CharReader &reader, ParserContext &ctx) +Rooted<Node> XmlParser::doParse(CharReader &reader, ParserContext &ctx)  {  	// Create the parser object  	ScopedExpatXmlParser p{"UTF-8"};  	// Create the parser stack instance and pass the reference to the state  	// machine descriptor -	ParserStack stack{ctx, XML_HANDLERS}; +	XMLParserUserData data; +	data.sourceId = reader.getSourceId(); + +	ParserStack stack{ctx, XML_HANDLERS, &data};  	XML_SetUserData(&p, &stack);  	XML_UseParserAsHandlerArg(&p); @@ -327,15 +332,14 @@ Rooted<Node> XmlParser::parse(CharReader &reader, ParserContext &ctx)  		// Parse the data and handle any XML error  		if (!XML_ParseBuffer(&p, bytesRead, bytesRead == 0)) { -			// Fetch the current line number and column -			int line = XML_GetCurrentLineNumber(&p); -			int column = XML_GetCurrentColumnNumber(&p); +			// Fetch the xml parser byte offset  			size_t offs = XML_GetCurrentByteIndex(&p);  			// Throw a corresponding exception  			XML_Error code = XML_GetErrorCode(&p);  			std::string msg = std::string{XML_ErrorString(code)}; -			throw LoggableException{"XML: " + msg, line, column, offs}; +			throw LoggableException{"XML: " + msg, +			                        SourceLocation{reader.getSourceId(), offs}};  		}  		// Abort once there are no more bytes in the stream @@ -346,6 +350,4 @@ Rooted<Node> XmlParser::parse(CharReader &reader, ParserContext &ctx)  	return nullptr;  }  } -} -} diff --git a/src/plugins/xml/XmlParser.hpp b/src/plugins/xml/XmlParser.hpp index 62f0128..3c0ffb7 100644 --- a/src/plugins/xml/XmlParser.hpp +++ b/src/plugins/xml/XmlParser.hpp @@ -31,23 +31,13 @@  #include <core/parser/Parser.hpp>  namespace ousia { -namespace parser { -namespace xml {  /**   * The XmlParser class implements parsing the various types of Ousía XML   * documents using the expat stream XML parser.   */  class XmlParser : public Parser { -public: -	/** -	 * Returns the mimetype supported by the XmlParser which is -	 * "text/vnd.ousia.oxm" and "text/vnd.ousia.oxd". -	 * -	 * @return a list containing the mimetype supported by Ousía. -	 */ -	std::set<std::string> mimetypes() override; - +protected:  	/**  	 * Parses the given input stream as XML file and returns the parsed  	 * top-level node. @@ -56,14 +46,10 @@ public:  	 * @param ctx is a reference to the ParserContext instance that should be  	 * used.  	 */ -	Rooted<Node> parse(CharReader &reader, ParserContext &ctx) override; - -	using Parser::parse; +	Rooted<Node> doParse(CharReader &reader, ParserContext &ctx) override;  };  } -} -}  #endif /* _OUSIA_XML_PARSER_HPP_ */ diff --git a/test/core/CodeTokenizerTest.cpp b/test/core/CodeTokenizerTest.cpp index 4d11622..2d4d5a7 100644 --- a/test/core/CodeTokenizerTest.cpp +++ b/test/core/CodeTokenizerTest.cpp @@ -38,7 +38,7 @@ TEST(CodeTokenizer, testTokenizer)  	    " */\n"                                 // 3  	    "var my_string = 'My \\'String\\'';\n"  // 4  	    "// and a line comment\n"               // 5 -	    "var my_obj = { a = 4;}"};              // 6 +	    "var my_obj = { a = 4;}", 0};              // 6  	//   123456789012345678901234567890123456789  	//   0        1         2         3  	TokenTreeNode root{{{"/*", 1}, @@ -60,40 +60,39 @@ TEST(CodeTokenizer, testTokenizer)  	    {6, {CodeTokenMode::LINEBREAK, LINEBREAK}}};  	std::vector<Token> expected = { -	    {BLOCK_COMMENT, "*\n * Some Block Comment\n ", 1, 1, 4, 3}, -	    {LINEBREAK, "\n", 4, 3, 1, 4}, -	    {TOKEN_TEXT, "var", 1, 4, 4, 4}, -	    {TOKEN_TEXT, "my_string", 5, 4, 14, 4}, -	    {TOKEN_TEXT, "=", 15, 4, 16, 4}, -	    {STRING, "My 'String'", 17, 4, 32, 4}, -	    {TOKEN_TEXT, ";", 32, 4, 33, 4}, -	    {LINEBREAK, "\n", 33, 4, 1, 5}, +	    {BLOCK_COMMENT, "*\n * Some Block Comment\n ", SourceLocation{0, 0, 29}}, +	    {LINEBREAK, "\n", SourceLocation{0, 29, 30}}, +	    {TOKEN_TEXT, "var", SourceLocation{0, 30, 33}}, +	    {TOKEN_TEXT, "my_string", SourceLocation{0, 34, 43}}, +	    {TOKEN_TEXT, "=", SourceLocation{0, 44, 45}}, +	    {STRING, "My 'String'", SourceLocation{0, 46, 61}}, +	    {TOKEN_TEXT, ";", SourceLocation{0, 61, 62}}, +	    {LINEBREAK, "\n", SourceLocation{0, 62, 63}},  	    // this is slightly counter-intuitive but makes sense if you think about  	    // it: As a line comment is ended by a line break the line break is  	    // technically still a part of the line comment and thus the ending  	    // is in the next line. -	    {LINE_COMMENT, " and a line comment", 1, 5, 1, 6}, -	    {TOKEN_TEXT, "var", 1, 6, 4, 6}, -	    {TOKEN_TEXT, "my_obj", 5, 6, 11, 6}, -	    {TOKEN_TEXT, "=", 12, 6, 13, 6}, -	    {CURLY_OPEN, "{", 14, 6, 15, 6}, -	    {TOKEN_TEXT, "a", 16, 6, 17, 6}, -	    {TOKEN_TEXT, "=", 18, 6, 19, 6}, -	    {TOKEN_TEXT, "4;", 20, 6, 22, 6}, -	    {CURLY_CLOSE, "}", 22, 6, 23, 6}, +	    {LINE_COMMENT, " and a line comment", SourceLocation{0, 63, 85}}, +	    {TOKEN_TEXT, "var", SourceLocation{0, 85, 88}}, +	    {TOKEN_TEXT, "my_obj", SourceLocation{0, 89, 95}}, +	    {TOKEN_TEXT, "=", SourceLocation{0, 96, 97}}, +	    {CURLY_OPEN, "{", SourceLocation{0, 98, 99}}, +	    {TOKEN_TEXT, "a", SourceLocation{0, 100, 101}}, +	    {TOKEN_TEXT, "=", SourceLocation{0, 102, 103}}, +	    {TOKEN_TEXT, "4;", SourceLocation{0, 104, 106}}, +	    {CURLY_CLOSE, "}", SourceLocation{0, 106, 107}},  	};  	CodeTokenizer tokenizer{reader, root, descriptors};  	Token t;  	for (auto &te : expected) { -		ASSERT_TRUE(tokenizer.next(t)); -		ASSERT_EQ(te.tokenId, t.tokenId); -		ASSERT_EQ(te.content, t.content); -		ASSERT_EQ(te.startColumn, t.startColumn); -		ASSERT_EQ(te.startLine, t.startLine); -		ASSERT_EQ(te.endColumn, t.endColumn); -		ASSERT_EQ(te.endLine, t.endLine); +		EXPECT_TRUE(tokenizer.next(t)); +		EXPECT_EQ(te.tokenId, t.tokenId); +		EXPECT_EQ(te.content, t.content); +		EXPECT_EQ(te.location.getSourceId(), t.location.getSourceId()); +		EXPECT_EQ(te.location.getStart(), t.location.getStart()); +		EXPECT_EQ(te.location.getEnd(), t.location.getEnd());  	}  	ASSERT_FALSE(tokenizer.next(t));  } diff --git a/test/core/RegistryTest.cpp b/test/core/RegistryTest.cpp index 45e09d3..21195f2 100644 --- a/test/core/RegistryTest.cpp +++ b/test/core/RegistryTest.cpp @@ -20,18 +20,83 @@  #include <sstream> +#include <core/common/Exceptions.hpp> +#include <core/parser/Parser.hpp> +#include <core/parser/ParserContext.hpp>  #include <core/resource/ResourceLocator.hpp>  #include <core/Registry.hpp>  namespace ousia { +namespace { +class TestParser : public Parser { +protected: +	Rooted<Node> doParse(CharReader &reader, ParserContext &ctx) override +	{ +		return new Node{ctx.manager}; +	} +}; +} + +static const Rtti rtti1{"rtti1"}; +static const Rtti rtti2{"rtti2"}; + +TEST(Registry, parsers) +{ +	Registry registry; + +	TestParser parser1; +	TestParser parser2; + +	registry.registerParser({"text/vnd.ousia.oxm", "text/vnd.ousia.oxd"}, +	                        {&rtti1, &rtti2}, &parser1); +	registry.registerParser({"text/vnd.ousia.opd"}, {&rtti2}, &parser2); + +	ASSERT_THROW( +	    registry.registerParser({"text/vnd.ousia.opd"}, {&rtti2}, &parser1), +	    OusiaException); + +	{ +		auto res = registry.getParserForMimetype("text/vnd.ousia.oxm"); +		ASSERT_EQ(&parser1, res.first); +		ASSERT_EQ(RttiSet({&rtti1, &rtti2}), res.second); +	} + +	{ +		auto res = registry.getParserForMimetype("text/vnd.ousia.opd"); +		ASSERT_EQ(&parser2, res.first); +		ASSERT_EQ(RttiSet({&rtti2}), res.second); +	} + +	{ +		auto res = registry.getParserForMimetype("application/javascript"); +		ASSERT_EQ(nullptr, res.first); +		ASSERT_EQ(RttiSet({}), res.second); +	} +} + +TEST(Registry, extensions) +{ +	Registry registry; + +	registry.registerExtension("oxm", "text/vnd.ousia.oxm"); +	registry.registerExtension("oxd", "text/vnd.ousia.oxd"); +	ASSERT_EQ("text/vnd.ousia.oxm", registry.getMimetypeForExtension("oxm")); +	ASSERT_EQ("text/vnd.ousia.oxm", registry.getMimetypeForExtension("OXM")); +	ASSERT_EQ("text/vnd.ousia.oxd", registry.getMimetypeForExtension("OxD")); +	ASSERT_EQ("", registry.getMimetypeForExtension("pdf")); + +	ASSERT_THROW(registry.registerExtension("oxm", "text/vnd.ousia.oxm"), +	             OusiaException); +} +  TEST(Registry, locateResource)  {  	StaticResourceLocator locator;  	locator.store("path", "test");  	Registry registry; -	registry.registerResourceLocator(locator); +	registry.registerResourceLocator(&locator);  	Resource res;  	ASSERT_TRUE( diff --git a/test/core/TokenizerTest.cpp b/test/core/TokenizerTest.cpp index 2b80662..d6e9306 100644 --- a/test/core/TokenizerTest.cpp +++ b/test/core/TokenizerTest.cpp @@ -65,29 +65,28 @@ TEST(Tokenizer, testTokenization)  {  	TokenTreeNode root{{{"/", 1}, {"/*", 2}, {"*/", 3}}}; -	CharReader reader{"Test/Test /* Block Comment */"}; -	//                 12345678901234567890123456789 +	CharReader reader{"Test/Test /* Block Comment */", 0}; +	//                 012345678901234567890123456789  	//                 0        1         2  	std::vector<Token> expected = { -	    {TOKEN_TEXT, "Test", 1, 1, 5, 1}, -	    {1, "/", 5, 1, 6, 1}, -	    {TOKEN_TEXT, "Test ", 6, 1, 11, 1}, -	    {2, "/*", 11, 1, 13, 1}, -	    {TOKEN_TEXT, " Block Comment ", 13, 1, 28, 1}, -	    {3, "*/", 28, 1, 30, 1}}; +	    {TOKEN_TEXT, "Test", SourceLocation{0, 0, 4}}, +	    {1, "/", SourceLocation{0, 4, 5}}, +	    {TOKEN_TEXT, "Test ", SourceLocation{0, 5, 10}}, +	    {2, "/*", SourceLocation{0, 10, 12}}, +	    {TOKEN_TEXT, " Block Comment ", SourceLocation{0, 12, 27}}, +	    {3, "*/", SourceLocation{0, 27, 29}}};  	Tokenizer tokenizer{reader, root};  	Token t;  	for (auto &te : expected) { -		ASSERT_TRUE(tokenizer.next(t)); -		ASSERT_EQ(te.tokenId, t.tokenId); -		ASSERT_EQ(te.content, t.content); -		ASSERT_EQ(te.startColumn, t.startColumn); -		ASSERT_EQ(te.startLine, t.startLine); -		ASSERT_EQ(te.endColumn, t.endColumn); -		ASSERT_EQ(te.endLine, t.endLine); +		EXPECT_TRUE(tokenizer.next(t)); +		EXPECT_EQ(te.tokenId, t.tokenId); +		EXPECT_EQ(te.content, t.content); +		EXPECT_EQ(te.location.getSourceId(), t.location.getSourceId()); +		EXPECT_EQ(te.location.getStart(), t.location.getStart()); +		EXPECT_EQ(te.location.getEnd(), t.location.getEnd());  	}  	ASSERT_FALSE(tokenizer.next(t));  } @@ -96,23 +95,22 @@ TEST(Tokenizer, testIncompleteTokens)  {  	TokenTreeNode root{{{"ab", 1}, {"c", 2}}}; -	CharReader reader{"ac"}; +	CharReader reader{"ac", 0};  	std::vector<Token> expected = { -	    {TOKEN_TEXT, "a", 1, 1, 2, 1}, -	    {2, "c", 2, 1, 3, 1}}; +	    {TOKEN_TEXT, "a", SourceLocation{0, 0, 1}}, +	    {2, "c", SourceLocation{0, 1, 2}}};  	Tokenizer tokenizer{reader, root};  	Token t;  	for (auto &te : expected) { -		ASSERT_TRUE(tokenizer.next(t)); -		ASSERT_EQ(te.tokenId, t.tokenId); -		ASSERT_EQ(te.content, t.content); -		ASSERT_EQ(te.startColumn, t.startColumn); -		ASSERT_EQ(te.startLine, t.startLine); -		ASSERT_EQ(te.endColumn, t.endColumn); -		ASSERT_EQ(te.endLine, t.endLine); +		EXPECT_TRUE(tokenizer.next(t)); +		EXPECT_EQ(te.tokenId, t.tokenId); +		EXPECT_EQ(te.content, t.content); +		EXPECT_EQ(te.location.getSourceId(), t.location.getSourceId()); +		EXPECT_EQ(te.location.getStart(), t.location.getStart()); +		EXPECT_EQ(te.location.getEnd(), t.location.getEnd());  	}  	ASSERT_FALSE(tokenizer.next(t));  } diff --git a/test/core/common/CharReaderTest.cpp b/test/core/common/CharReaderTest.cpp index 702d958..fba60f9 100644 --- a/test/core/common/CharReaderTest.cpp +++ b/test/core/common/CharReaderTest.cpp @@ -453,9 +453,8 @@ TEST(CharReader, simpleRead)  	// The two strings must equal  	ASSERT_EQ(testStr, res); -	// We must now be at line 1, column 15 -	ASSERT_EQ(1, reader.getLine()); -	ASSERT_EQ((int)(testStr.size() + 1), reader.getColumn()); +	// Check the char reader offset +	ASSERT_EQ(testStr.size(), reader.getOffset());  	// If we call either read or peek, false is returned  	ASSERT_FALSE(reader.read(c)); @@ -483,15 +482,11 @@ TEST(CharReader, simplePeek)  	ASSERT_EQ(testStr, res);  	// We must now be at line 1, column 1 and NOT at the end of the stream -	ASSERT_EQ(1, reader.getLine()); -	ASSERT_EQ(1, reader.getColumn()); +	ASSERT_EQ(0, reader.getOffset());  	ASSERT_FALSE(reader.atEnd()); -	// If we consume the peek, we must be at line 1, column 15 and we should be -	// at the end of the stream  	reader.consumePeek(); -	ASSERT_EQ(1, reader.getLine()); -	ASSERT_EQ((int)(testStr.size() + 1), reader.getColumn()); +	ASSERT_EQ(testStr.size(), reader.getOffset());  	ASSERT_TRUE(reader.atEnd());  	// If we call either read or peek, false is returned @@ -499,64 +494,6 @@ TEST(CharReader, simplePeek)  	ASSERT_FALSE(reader.peek(c));  } -TEST(CharReader, rowColumnCounter) -{ -	// Feed a test string into the reader -	CharReader reader{"1\n\r2\n3\r\n\n4"}; - -	// We should currently be in line 1, column 1 -	ASSERT_EQ(1, reader.getLine()); -	ASSERT_EQ(1, reader.getColumn()); - -	// Read two characters -	char c; -	for (int i = 0; i < 2; i++) -		reader.read(c); -	ASSERT_EQ(2, reader.getLine()); -	ASSERT_EQ(1, reader.getColumn()); - -	// Read two characters -	for (int i = 0; i < 2; i++) -		reader.read(c); -	ASSERT_EQ(3, reader.getLine()); -	ASSERT_EQ(1, reader.getColumn()); - -	// Read three characters -	for (int i = 0; i < 3; i++) -		reader.read(c); -	ASSERT_EQ(5, reader.getLine()); -	ASSERT_EQ(1, reader.getColumn()); -} - -TEST(CharReader, rowColumnCounterTest) -{ -	// Feed a test string into the reader -	CharReader reader{"1\n\r2\n3\r\n\n4", 4, 10}; - -	// We should currently be in line 1, column 1 -	ASSERT_EQ(4, reader.getLine()); -	ASSERT_EQ(10, reader.getColumn()); - -	// Read two characters -	char c; -	for (int i = 0; i < 2; i++) -		reader.read(c); -	ASSERT_EQ(5, reader.getLine()); -	ASSERT_EQ(1, reader.getColumn()); - -	// Read two characters -	for (int i = 0; i < 2; i++) -		reader.read(c); -	ASSERT_EQ(6, reader.getLine()); -	ASSERT_EQ(1, reader.getColumn()); - -	// Read three characters -	for (int i = 0; i < 3; i++) -		reader.read(c); -	ASSERT_EQ(8, reader.getLine()); -	ASSERT_EQ(1, reader.getColumn()); -} -  TEST(CharReader, linebreakSubstitution)  {  	// Feed a test string into the reader and read all characters back @@ -571,23 +508,6 @@ TEST(CharReader, linebreakSubstitution)  	ASSERT_EQ("this\nis\njust\na test\n\ntest\n", res);  } -TEST(CharReader, rowColumnCounterUTF8) -{ -	// Feed a test string with some umlauts into the reader -	CharReader reader{"\x61\xc3\x96\xc3\x84\xc3\x9c\xc3\x9f"}; - -	// Read all bytes -	char c; -	while (reader.read(c)) { -		// Do nothing -	} - -	// The sequence above equals 5 UTF-8 characters (so after reading all the -	// cursor is at position 6) -	ASSERT_EQ(1, reader.getLine()); -	ASSERT_EQ(6, reader.getColumn()); -} -  TEST(CharReader, stream)  {  	// Copy the test data to a string stream @@ -608,8 +528,8 @@ TEST(CharReader, stream)  TEST(CharReader, fork)  {  	std::string testStr{"first line\n\n\rsecond line\n\rlast line"}; -	//                   0123456789 0   123456789012   3456789012 -	//                   0         1             2              3 +	//                   0123456789 0 1 234567890123 4 5678901234 +	//                   0          1           2           3  	char c;  	CharReader reader{testStr}; @@ -626,8 +546,7 @@ TEST(CharReader, fork)  	{  		CharReaderFork fork = reader.fork(); -		ASSERT_EQ(1, fork.getLine()); -		ASSERT_EQ(5, fork.getColumn()); +		ASSERT_EQ(4, fork.getOffset());  		fork.peek(c);  		ASSERT_EQ('i', c); @@ -635,11 +554,7 @@ TEST(CharReader, fork)  		fork.read(c);  		ASSERT_EQ('t', c); -		ASSERT_EQ(1, fork.getLine()); -		ASSERT_EQ(6, fork.getColumn()); - -		ASSERT_EQ(1, reader.getLine()); -		ASSERT_EQ(5, reader.getColumn()); +		ASSERT_EQ(5, fork.getOffset());  		reader.read(c);  		reader.read(c); @@ -647,11 +562,10 @@ TEST(CharReader, fork)  		fork.commit();  	} -	ASSERT_EQ(1, reader.getLine()); -	ASSERT_EQ(6, reader.getColumn()); +	ASSERT_EQ(5, reader.getOffset());  } -TEST(CharReaderTest, context) +/*TEST(CharReader, context)  {  	std::string testStr{"first line\n\n\rsecond line\n\rlast line"};  	//                   0123456789 0   123456789012   3456789012 @@ -816,6 +730,7 @@ TEST(CharReaderTest, context)  		ASSERT_TRUE(ctx.truncatedStart);  		ASSERT_FALSE(ctx.truncatedEnd);  	} -} +}*/ +  } diff --git a/test/core/common/LoggerTest.cpp b/test/core/common/LoggerTest.cpp index 66e49cd..9b20cc6 100644 --- a/test/core/common/LoggerTest.cpp +++ b/test/core/common/LoggerTest.cpp @@ -33,32 +33,30 @@ struct Pos {  	SourceLocation getLocation() { return pos; }  }; -static SourceContext contextCallback(const SourceLocation &location, -	                                     void *) +static SourceContext contextCallback(const SourceLocation &location)  { -	return SourceContext{"int bla = blub;", 10, true, false}; +	SourceContext ctx; +	ctx.filename = "testfile.test"; +	ctx.startLine = 10; +	ctx.endLine = 10; +	ctx.startColumn = 20; +	ctx.endColumn = 20; +	return ctx;  }  TEST(TerminalLogger, log)  {  	// Test for manual visual expection only -- no assertions  	TerminalLogger logger{std::cerr, true}; -	logger.pushFile("test.odp"); +	logger.setSourceContextCallback(contextCallback); -	logger.debug("This is a test debug message", SourceLocation{10, 20}); -	logger.debug("This is a test debug message with no column", -	             SourceLocation{10}); -	logger.debug("This is a test debug message with no line"); -	logger.note("This is a test note", SourceLocation{10, 20}); -	logger.warning("This is a test warning", SourceLocation{10, 20}); -	logger.error("This is a test error", SourceLocation{10, 20}); -	logger.fatalError("This is a test fatal error!", SourceLocation{10, 20}); +	logger.debug("This is a test debug message"); +	logger.note("This is a test note"); +	logger.warning("This is a test warning"); +	logger.error("This is a test error"); +	logger.fatalError("This is a test fatal error!"); -	logger.pushFile("test2.odp", SourceLocation{}, contextCallback); -	logger.error("This is a test error with context", SourceLocation{10, 20}); -	logger.popFile(); - -	Pos pos(SourceLocation{10, 20}); +	logger.error("This is a test error with context");  	try {  		throw LoggableException{"An exception"}; @@ -66,15 +64,6 @@ TEST(TerminalLogger, log)  	catch (const LoggableException &ex) {  		logger.log(ex);  	} - -	try { -		throw LoggableException{"An exception at position", pos}; -	} -	catch (const LoggableException &ex) { -		logger.log(ex); -	} - -	logger.log(Severity::ERROR, "This is a positioned log message", pos);  }  TEST(TerminalLogger, fork) @@ -82,16 +71,11 @@ TEST(TerminalLogger, fork)  	// Test for manual visual expection only -- no assertions  	TerminalLogger logger{std::cerr, true}; +	logger.setSourceContextCallback(contextCallback); +  	LoggerFork fork = logger.fork(); -	fork.pushFile("test.odp", SourceLocation{}, contextCallback); -	fork.error("This is a test error with context", SourceLocation{10, 20}); -	fork.pushFile("test2.odp"); -	fork.error("This is a test error without context"); -	fork.popFile(); -	fork.error("Another error"); -	fork.popFile(); -	fork.error("Another error"); +	fork.error("This is a test error with context");  	// Print all error messages  	fork.commit(); diff --git a/test/core/common/UtilsTest.cpp b/test/core/common/UtilsTest.cpp index 53beb79..c8f6922 100644 --- a/test/core/common/UtilsTest.cpp +++ b/test/core/common/UtilsTest.cpp @@ -54,5 +54,24 @@ TEST(Utils, split)  	ASSERT_EQ(std::vector<std::string>({"", "a", "be", "c", ""}),  	          Utils::split(".a.be.c.", '.'));  } + +TEST(Utils, toLower) +{ +	ASSERT_EQ("", Utils::toLower("")); +	ASSERT_EQ("foo00", Utils::toLower("foo00")); +	ASSERT_EQ("foo00", Utils::toLower("fOO00")); +} + +TEST(Utils, extractFileExtension) +{ +	ASSERT_EQ("", Utils::extractFileExtension("")); +	ASSERT_EQ("", Utils::extractFileExtension("test")); +	ASSERT_EQ("ext", Utils::extractFileExtension("test.ext")); +	ASSERT_EQ("", Utils::extractFileExtension("foo.bar/test")); +	ASSERT_EQ("", Utils::extractFileExtension("foo.bar\\test")); +	ASSERT_EQ("ext", Utils::extractFileExtension("foo.bar/test.ext")); +	ASSERT_EQ("ext", Utils::extractFileExtension("foo.bar/test.EXT")); +} +  } diff --git a/test/core/parser/ParserStackTest.cpp b/test/core/parser/ParserStackTest.cpp index 69978b0..81160da 100644 --- a/test/core/parser/ParserStackTest.cpp +++ b/test/core/parser/ParserStackTest.cpp @@ -24,7 +24,6 @@  #include <core/parser/StandaloneParserContext.hpp>  namespace ousia { -namespace parser {  static const State STATE_DOCUMENT = 0;  static const State STATE_BODY = 1; @@ -168,7 +167,5 @@ TEST(ParserStack, validation)  	ASSERT_FALSE(logger.hasError());  	s.end();  } - -}  } diff --git a/test/core/parser/StandaloneParserContext.hpp b/test/core/parser/StandaloneParserContext.hpp index 347d34f..51cd1e6 100644 --- a/test/core/parser/StandaloneParserContext.hpp +++ b/test/core/parser/StandaloneParserContext.hpp @@ -23,16 +23,18 @@  #include <core/model/Project.hpp>  #include <core/parser/Parser.hpp> +#include <core/parser/ParserScope.hpp> +#include <core/parser/ParserContext.hpp> +#include <core/Registry.hpp>  namespace ousia { -namespace parser {  struct StandaloneParserContext {  public:  	Manager manager;  	Logger logger; -	Scope scope;  	Registry registry; +	ParserScope scope;  	Rooted<model::Project> project;  	ParserContext context; @@ -47,7 +49,6 @@ public:  	      context(scope, registry, externalLogger, manager, project){};  };  } -}  #endif /* _OUSIA_STANDALONE_PARSER_CONTEXT_ */ diff --git a/test/plugins/css/CSSParserTest.cpp b/test/plugins/css/CSSParserTest.cpp index 84522b3..420241e 100644 --- a/test/plugins/css/CSSParserTest.cpp +++ b/test/plugins/css/CSSParserTest.cpp @@ -26,8 +26,6 @@  #include <core/parser/StandaloneParserContext.hpp>  namespace ousia { -namespace parser { -namespace css {  TEST(CSSParser, testParseSelectors)  {  	// create a string describing a SelectorTree @@ -268,8 +266,7 @@ void assertException(std::string css)  	CharReader reader(css);  	TerminalLogger logger(std::cerr, true);  	{ -		ScopedLogger sl(logger, "test.css", SourceLocation{}, -		                CharReader::contextCallback, &reader); +		ScopedLogger sl(logger);  		StandaloneParserContext ctx(sl);  		CSSParser instance; @@ -296,5 +293,3 @@ TEST(CSSParser, testParseExceptions)  	assertException("A > ");  }  } -} -} diff --git a/test/plugins/filesystem/FileLocatorTest.cpp b/test/plugins/filesystem/FileLocatorTest.cpp index 17d43dd..beb091d 100644 --- a/test/plugins/filesystem/FileLocatorTest.cpp +++ b/test/plugins/filesystem/FileLocatorTest.cpp @@ -142,6 +142,26 @@ TEST(FileLocator, testLocate)  	assert_not_located(locator, "c.txt", "", ResourceType::SCRIPT);  } +TEST(FileLocator, testLocateRelative) +{ +	FileLocator locator; +	locator.addUnittestSearchPath("filesystem"); + +	// Add the respective search path +	locator.addUnittestSearchPath("filesystem/b"); + +	Resource resA, resC; +	ASSERT_TRUE(locator.locate(resA, "a.txt")); +	ASSERT_TRUE(locator.locate(resC, "c.txt")); + +	Resource resD; +	ASSERT_TRUE(locator.locate(resD, "d.txt")); +	ASSERT_TRUE(locator.locate(resD, "d.txt", ResourceType::UNKNOWN, resA)); +	ASSERT_TRUE(locator.locate(resD, "d.txt", ResourceType::UNKNOWN, resC)); +	ASSERT_FALSE(locator.locate(resD, "./d.txt", ResourceType::UNKNOWN, resA)); +	ASSERT_TRUE(locator.locate(resD, "./d.txt", ResourceType::UNKNOWN, resC)); +} +  TEST(FileLocator, testStream)  {  	FileLocator locator; diff --git a/test/plugins/xml/XmlParserTest.cpp b/test/plugins/xml/XmlParserTest.cpp index f1956e0..52b64e5 100644 --- a/test/plugins/xml/XmlParserTest.cpp +++ b/test/plugins/xml/XmlParserTest.cpp @@ -20,14 +20,13 @@  #include <gtest/gtest.h> +#include <core/common/CharReader.hpp>  #include <core/common/Logger.hpp>  #include <core/parser/StandaloneParserContext.hpp>  #include <plugins/xml/XmlParser.hpp>  namespace ousia { -namespace parser { -namespace xml {  static TerminalLogger logger(std::cerr, true); @@ -41,7 +40,6 @@ TEST(XmlParser, mismatchedTagException)  		p.parse("<document>\n</document2>", ctx.context);  	}  	catch (LoggableException ex) { -		ASSERT_EQ(2, ex.loc.line);  		hadException = true;  	}  	ASSERT_TRUE(hadException); @@ -82,8 +80,6 @@ TEST(XmlParser, namespaces)  	XmlParser p;  	CharReader reader(TEST_DATA);  	{ -		ScopedLogger sl(logger, "test.oxd", SourceLocation{}, -		                CharReader::contextCallback, &reader);  		try {  			p.parse(TEST_DATA, ctx.context);  		} @@ -94,6 +90,4 @@ TEST(XmlParser, namespaces)  	}  }  } -} -} diff --git a/testdata/filesystem/b/d.txt b/testdata/filesystem/b/d.txt new file mode 100644 index 0000000..3e676fe --- /dev/null +++ b/testdata/filesystem/b/d.txt @@ -0,0 +1 @@ +file d | 
