Move AssemblerBase to separate compilation unit
MODIFY
src/assembler/assembler.h
src/assembler/assembler.h
#include <QRegularExpression>
-#include "assembler_defines.h"
-#include "directive.h"
-#include "expreval.h"
#include "instruction.h"
#include "isa/isainfo.h"
#include "matcher.h"
#include <set>
#include <variant>
+#include "assemblerbase.h"
+
namespace Ripes {
namespace Assembler {
@@ -70,253 +69,6 @@ namespace Assembler {
using _AssembleRes = AssembleRes<_Reg_T>; \
using _InstrRes = InstrRes<_Reg_T>;
-struct OpDisassembleResult {
- /// Stringified representation of a disassembled machine word.
- QString repr;
- /// Number of bytes disassembled.
- unsigned bytesDisassembled;
- /// Optional error that occured during disassembly.
- std::optional<Error> err;
-};
-
-class AssemblerBase {
-public:
- AssemblerBase() {
- /// Set the source line splitter regex and immediately JIT compile it.
- /// Regex: Split on all empty strings (\s+) and characters [, \[, \], \(, \)] except for quote-delimitered
- /// substrings.
- m_splitterRegex = QRegularExpression(R"((\s+|\,)(?=(?:[^"]*"[^"]*")*[^"]*$))");
- m_splitterRegex.optimize();
- }
- virtual ~AssemblerBase() {}
- std::optional<Error> setCurrentSegment(Section seg) const {
- if (m_sectionBasePointers.count(seg) == 0) {
- return Error(0, "No base address set for segment '" + seg + +"'");
- }
- m_currentSection = seg;
- return {};
- }
-
- /// Sets the base pointer of seg to the provided 'base' value.
- void setSegmentBase(Section seg, AInt base) { m_sectionBasePointers[seg] = base; }
-
- /// Assembles an input program (represented as a list of strings). Optionally, a set of predefined symbols may be
- /// provided to the assemble call.
- virtual AssembleResult assemble(const QStringList& programLines, const SymbolMap* symbols = nullptr) const = 0;
- AssembleResult assembleRaw(const QString& program, const SymbolMap* symbols = nullptr) const {
- const auto programLines = program.split(QRegExp("[\r\n]"));
- return assemble(programLines, symbols);
- }
-
- /// Disassembles an input program relative to the provided base address.
- virtual DisassembleResult disassemble(const Program& program, const AInt baseAddress = 0) const = 0;
-
- /// Disassembles an input word using the provided symbol mapping relative to the provided base address.
- virtual OpDisassembleResult disassemble(const VInt word, const ReverseSymbolMap& symbols,
- const AInt baseAddress = 0) const = 0;
-
- /// Returns the set of opcodes (as strings) which are supported by this assembler.
- virtual std::set<QString> getOpcodes() const = 0;
-
- /// Adds a symbol to the current symbol mapping of this assembler defined at the 'line' in the input program.
- std::optional<Error> addSymbol(const TokenizedSrcLine& line, Symbol s, VInt v) const {
- return addSymbol(line.sourceLine, s, v);
- }
-
- /// Adds a symbol to the current symbol mapping of this assembler.
- std::optional<Error> addSymbol(const unsigned& line, Symbol s, VInt v) const {
- if (m_symbolMap.count(s)) {
- return {Error(line, "Multiple definitions of symbol '" + s.v + "'")};
- }
- m_symbolMap[s] = v;
- return {};
- }
-
- /// Resolves an expression through either the built-in symbol map, or through the expression evaluator.
- ExprEvalRes evalExpr(const QString& expr) const {
- auto symbolValue = m_symbolMap.find(expr);
- if (symbolValue != m_symbolMap.end()) {
- return symbolValue->second;
- } else {
- return evaluate(expr, &m_symbolMap);
- }
- }
-
- void setDirectives(DirectiveVec& directives) {
- if (m_directives.size() != 0) {
- throw std::runtime_error("Directives already set");
- }
- m_directives = directives;
- for (const auto& iter : m_directives) {
- const auto directive = iter.get()->name();
- if (m_directivesMap.count(directive) != 0) {
- throw std::runtime_error("Error: directive " + directive.toStdString() +
- " has already been registerred.");
- }
- m_directivesMap[directive] = iter;
- if (iter->early()) {
- m_earlyDirectives.insert(iter->name());
- }
- }
- }
-
-protected:
- /// Creates a set of LineTokens by tokenizing a line of source code.
- QRegularExpression m_splitterRegex;
- std::variant<Error, LineTokens> tokenize(const QString& line, const int sourceLine) const {
- auto tokens = line.split(m_splitterRegex);
- tokens.removeAll(QStringLiteral(""));
- auto joinedtokens = joinParentheses(tokens);
- if (auto* err = std::get_if<Error>(&joinedtokens)) {
- err->first = sourceLine;
- return *err;
- }
- return std::get<LineTokens>(joinedtokens);
- }
-
- HandleDirectiveRes assembleDirective(const DirectiveArg& arg, bool& ok, bool skipEarlyDirectives = true) const {
- ok = false;
- if (arg.line.directive.isEmpty()) {
- return std::nullopt;
- }
- ok = true;
- try {
- const auto& directive = m_directivesMap.at(arg.line.directive);
- if (directive->early() && skipEarlyDirectives) {
- return std::nullopt;
- }
- return directive->handle(this, arg);
- } catch (const std::out_of_range&) {
- return {Error(arg.line.sourceLine, "Unknown directive '" + arg.line.directive + "'")};
- }
- }
- /**
- * @brief splitSymbolsFromLine
- * @returns a pair consisting of a symbol and the the input @p line tokens where the symbol has been removed.
- */
- virtual std::variant<Error, SymbolLinePair> splitSymbolsFromLine(const LineTokens& tokens, int sourceLine) const {
- if (tokens.size() == 0) {
- return {SymbolLinePair({}, tokens)};
- }
-
- // Handle the case where a token has been joined together with another token, ie "B:nop"
- LineTokens splitTokens;
- splitTokens.reserve(tokens.size());
- for (const auto& token : tokens) {
- Token buffer;
- for (const auto& ch : token) {
- buffer.append(ch);
- if (ch == ':') {
- splitTokens.push_back(buffer);
- buffer.clear();
- }
- }
- if (!buffer.isEmpty()) {
- splitTokens.push_back(buffer);
- }
- }
-
- LineTokens remainingTokens;
- remainingTokens.reserve(splitTokens.size());
- Symbols symbols;
- bool symbolStillAllowed = true;
- for (const auto& token : splitTokens) {
- if (token.endsWith(':')) {
- if (symbolStillAllowed) {
- const Symbol cleanedSymbol = Symbol(token.left(token.length() - 1), Symbol::Type::Address);
- if (symbols.count(cleanedSymbol.v) != 0) {
- return {Error(sourceLine, "Multiple definitions of symbol '" + cleanedSymbol.v + "'")};
- } else {
- if (cleanedSymbol.v.isEmpty() || cleanedSymbol.v.contains(s_exprOperatorsRegex)) {
- return {Error(sourceLine, "Invalid symbol '" + cleanedSymbol.v + "'")};
- }
-
- symbols.insert(cleanedSymbol);
- }
- } else {
- return {Error(sourceLine, QStringLiteral("Stray ':' in line"))};
- }
- } else {
- remainingTokens.push_back(token);
- symbolStillAllowed = false;
- }
- }
- return {SymbolLinePair(symbols, remainingTokens)};
- }
-
- virtual std::variant<Error, DirectiveLinePair> splitDirectivesFromLine(const LineTokens& tokens,
- int sourceLine) const {
- if (tokens.size() == 0) {
- return {DirectiveLinePair(QString(), tokens)};
- }
-
- LineTokens remainingTokens;
- remainingTokens.reserve(tokens.size());
- std::vector<QString> directives;
- bool directivesStillAllowed = true;
- for (const auto& token : tokens) {
- if (token.startsWith('.')) {
- if (directivesStillAllowed) {
- directives.push_back(token);
- } else {
- return {Error(sourceLine, QStringLiteral("Stray '.' in line"))};
- }
- } else {
- remainingTokens.push_back(token);
- directivesStillAllowed = false;
- }
- }
- if (directives.size() > 1) {
- return {Error(sourceLine, QStringLiteral("Illegal multiple directives"))};
- } else {
- return {DirectiveLinePair(directives.size() == 1 ? directives[0] : QString(), remainingTokens)};
- }
- }
-
- virtual std::variant<Error, LineTokens> splitCommentFromLine(const LineTokens& tokens) const {
- if (tokens.size() == 0) {
- return {tokens};
- }
-
- LineTokens preCommentTokens;
- preCommentTokens.reserve(tokens.size());
- for (const auto& token : tokens) {
- if (token.contains(commentDelimiter())) {
- break;
- } else {
- preCommentTokens.push_back(token);
- }
- }
- return {preCommentTokens};
- }
-
- virtual QChar commentDelimiter() const = 0;
-
- /**
- * @brief m_sectionBasePointers maintains the base position for the segments
- * annoted by the Segment enum class.
- */
- std::map<Section, AInt> m_sectionBasePointers;
- /**
- * @brief m_currentSegment maintains the current segment where the assembler emits information.
- * Marked mutable to allow for switching currently selected segment during assembling.
- */
- mutable Section m_currentSection;
-
- /**
- * @brief symbolMap maintains the symbols recorded during assembling. Marked mutable to allow for assembler
- * directives to add symbols during assembling.
- */
- mutable SymbolMap m_symbolMap;
-
- /**
- * @brief m_assemblerDirectives is the set of supported assembler directives.
- */
- DirectiveVec m_directives;
- DirectiveMap m_directivesMap;
- EarlyDirectives m_earlyDirectives;
-};
-
/**
* Reg_T: type equal in size to the register width of the target
* Instr_T: type equal in size to the instruction width of the target
@@ -324,8 +76,6 @@ protected:
template <typename Reg_T>
class Assembler : public AssemblerBase {
static_assert(std::numeric_limits<Reg_T>::is_integer, "Register type must be integer");
- static_assert(std::numeric_limits<Instr_T>::is_integer, "Instruction type must be integer");
-
ASSEMBLER_TYPES(Reg_T)
public:

ADD
src/assembler/assemblerbase.cpp
+#include "assemblerbase.h"
+
+#include "parserutilities.h"
+
+namespace Ripes {
+namespace Assembler {
+
+AssemblerBase::AssemblerBase() {
+ /// Set the source line splitter regex and immediately JIT compile it.
+ /// Regex: Split on all empty strings (\s+) and characters [, \[, \], \(, \)] except for quote-delimitered
+ /// substrings.
+ m_splitterRegex = QRegularExpression(R"((\s+|\,)(?=(?:[^"]*"[^"]*")*[^"]*$))");
+ m_splitterRegex.optimize();
+}
+
+std::optional<Error> AssemblerBase::setCurrentSegment(Section seg) const {
+ if (m_sectionBasePointers.count(seg) == 0) {
+ return Error(0, "No base address set for segment '" + seg + +"'");
+ }
+ m_currentSection = seg;
+ return {};
+}
+
+/// Sets the base pointer of seg to the provided 'base' value.
+void AssemblerBase::setSegmentBase(Section seg, AInt base) {
+ m_sectionBasePointers[seg] = base;
+}
+
+AssembleResult AssemblerBase::assembleRaw(const QString& program, const SymbolMap* symbols) const {
+ const auto programLines = program.split(QRegExp("[\r\n]"));
+ return assemble(programLines, symbols);
+}
+
+/// Adds a symbol to the current symbol mapping of this assembler defined at the 'line' in the input program.
+std::optional<Error> AssemblerBase::addSymbol(const TokenizedSrcLine& line, const Symbol& s, VInt v) const {
+ return addSymbol(line.sourceLine, s, v);
+}
+
+/// Adds a symbol to the current symbol mapping of this assembler.
+std::optional<Error> AssemblerBase::addSymbol(const unsigned& line, const Symbol& s, VInt v) const {
+ if (m_symbolMap.count(s)) {
+ return {Error(line, "Multiple definitions of symbol '" + s.v + "'")};
+ }
+ m_symbolMap[s] = v;
+ return {};
+}
+
+/// Resolves an expression through either the built-in symbol map, or through the expression evaluator.
+ExprEvalRes AssemblerBase::evalExpr(const QString& expr) const {
+ auto symbolValue = m_symbolMap.find(expr);
+ if (symbolValue != m_symbolMap.end()) {
+ return symbolValue->second;
+ } else {
+ return evaluate(expr, &m_symbolMap);
+ }
+}
+
+void AssemblerBase::setDirectives(const DirectiveVec& directives) {
+ if (m_directives.size() != 0) {
+ throw std::runtime_error("Directives already set");
+ }
+ m_directives = directives;
+ for (const auto& iter : m_directives) {
+ const auto directive = iter.get()->name();
+ if (m_directivesMap.count(directive) != 0) {
+ throw std::runtime_error("Error: directive " + directive.toStdString() + " has already been registerred.");
+ }
+ m_directivesMap[directive] = iter;
+ if (iter->early()) {
+ m_earlyDirectives.insert(iter->name());
+ }
+ }
+}
+
+std::variant<Error, LineTokens> AssemblerBase::tokenize(const QString& line, const int sourceLine) const {
+ auto tokens = line.split(m_splitterRegex);
+ tokens.removeAll(QStringLiteral(""));
+ auto joinedtokens = joinParentheses(tokens);
+ if (auto* err = std::get_if<Error>(&joinedtokens)) {
+ err->first = sourceLine;
+ return *err;
+ }
+ return std::get<LineTokens>(joinedtokens);
+}
+
+HandleDirectiveRes AssemblerBase::assembleDirective(const DirectiveArg& arg, bool& ok, bool skipEarlyDirectives) const {
+ ok = false;
+ if (arg.line.directive.isEmpty()) {
+ return std::nullopt;
+ }
+ ok = true;
+ try {
+ const auto& directive = m_directivesMap.at(arg.line.directive);
+ if (directive->early() && skipEarlyDirectives) {
+ return std::nullopt;
+ }
+ return directive->handle(this, arg);
+ } catch (const std::out_of_range&) {
+ return {Error(arg.line.sourceLine, "Unknown directive '" + arg.line.directive + "'")};
+ }
+}
+
+/**
+ * @brief splitSymbolsFromLine
+ * @returns a pair consisting of a symbol and the the input @p line tokens where the symbol has been removed.
+ */
+std::variant<Error, SymbolLinePair> AssemblerBase::splitSymbolsFromLine(const LineTokens& tokens,
+ int sourceLine) const {
+ if (tokens.size() == 0) {
+ return {SymbolLinePair({}, tokens)};
+ }
+
+ // Handle the case where a token has been joined together with another token, ie "B:nop"
+ LineTokens splitTokens;
+ splitTokens.reserve(tokens.size());
+ for (const auto& token : tokens) {
+ Token buffer;
+ for (const auto& ch : token) {
+ buffer.append(ch);
+ if (ch == ':') {
+ splitTokens.push_back(buffer);
+ buffer.clear();
+ }
+ }
+ if (!buffer.isEmpty()) {
+ splitTokens.push_back(buffer);
+ }
+ }
+
+ LineTokens remainingTokens;
+ remainingTokens.reserve(splitTokens.size());
+ Symbols symbols;
+ bool symbolStillAllowed = true;
+ for (const auto& token : splitTokens) {
+ if (token.endsWith(':')) {
+ if (symbolStillAllowed) {
+ const Symbol cleanedSymbol = Symbol(token.left(token.length() - 1), Symbol::Type::Address);
+ if (symbols.count(cleanedSymbol.v) != 0) {
+ return {Error(sourceLine, "Multiple definitions of symbol '" + cleanedSymbol.v + "'")};
+ } else {
+ if (cleanedSymbol.v.isEmpty() || cleanedSymbol.v.contains(s_exprOperatorsRegex)) {
+ return {Error(sourceLine, "Invalid symbol '" + cleanedSymbol.v + "'")};
+ }
+
+ symbols.insert(cleanedSymbol);
+ }
+ } else {
+ return {Error(sourceLine, QStringLiteral("Stray ':' in line"))};
+ }
+ } else {
+ remainingTokens.push_back(token);
+ symbolStillAllowed = false;
+ }
+ }
+ return {SymbolLinePair(symbols, remainingTokens)};
+}
+
+std::variant<Error, DirectiveLinePair> AssemblerBase::splitDirectivesFromLine(const LineTokens& tokens,
+ int sourceLine) const {
+ if (tokens.size() == 0) {
+ return {DirectiveLinePair(QString(), tokens)};
+ }
+
+ LineTokens remainingTokens;
+ remainingTokens.reserve(tokens.size());
+ std::vector<QString> directives;
+ bool directivesStillAllowed = true;
+ for (const auto& token : tokens) {
+ if (token.startsWith('.')) {
+ if (directivesStillAllowed) {
+ directives.push_back(token);
+ } else {
+ return {Error(sourceLine, QStringLiteral("Stray '.' in line"))};
+ }
+ } else {
+ remainingTokens.push_back(token);
+ directivesStillAllowed = false;
+ }
+ }
+ if (directives.size() > 1) {
+ return {Error(sourceLine, QStringLiteral("Illegal multiple directives"))};
+ } else {
+ return {DirectiveLinePair(directives.size() == 1 ? directives[0] : QString(), remainingTokens)};
+ }
+}
+
+std::variant<Error, LineTokens> AssemblerBase::splitCommentFromLine(const LineTokens& tokens) const {
+ if (tokens.size() == 0) {
+ return {tokens};
+ }
+
+ LineTokens preCommentTokens;
+ preCommentTokens.reserve(tokens.size());
+ for (const auto& token : tokens) {
+ if (token.contains(commentDelimiter())) {
+ break;
+ } else {
+ preCommentTokens.push_back(token);
+ }
+ }
+ return {preCommentTokens};
+}
+
+} // namespace Assembler
+} // namespace Ripes

ADD
src/assembler/assemblerbase.h
+#pragma once
+
+#include <QRegularExpression>
+
+#include <optional>
+
+#include "assembler_defines.h"
+#include "directive.h"
+#include "expreval.h"
+
+namespace Ripes {
+namespace Assembler {
+
+struct OpDisassembleResult {
+ /// Stringified representation of a disassembled machine word.
+ QString repr;
+ /// Number of bytes disassembled.
+ unsigned bytesDisassembled;
+ /// Optional error that occured during disassembly.
+ std::optional<Error> err;
+};
+
+/// Base class for a Ripes assembler.
+class AssemblerBase {
+public:
+ AssemblerBase();
+ virtual ~AssemblerBase() {}
+ std::optional<Error> setCurrentSegment(Section seg) const;
+
+ /// Sets the base pointer of seg to the provided 'base' value.
+ void setSegmentBase(Section seg, AInt base);
+
+ /// Assembles an input program (represented as a list of strings). Optionally, a set of predefined symbols may be
+ /// provided to the assemble call.
+ virtual AssembleResult assemble(const QStringList& programLines, const SymbolMap* symbols = nullptr) const = 0;
+ AssembleResult assembleRaw(const QString& program, const SymbolMap* symbols = nullptr) const;
+
+ /// Disassembles an input program relative to the provided base address.
+ virtual DisassembleResult disassemble(const Program& program, const AInt baseAddress = 0) const = 0;
+
+ /// Disassembles an input word using the provided symbol mapping relative to the provided base address.
+ virtual OpDisassembleResult disassemble(const VInt word, const ReverseSymbolMap& symbols,
+ const AInt baseAddress = 0) const = 0;
+
+ /// Returns the set of opcodes (as strings) which are supported by this assembler.
+ virtual std::set<QString> getOpcodes() const = 0;
+
+ /// Adds a symbol to the current symbol mapping of this assembler defined at the 'line' in the input program.
+ std::optional<Error> addSymbol(const TokenizedSrcLine& line, const Symbol& s, VInt v) const;
+
+ /// Adds a symbol to the current symbol mapping of this assembler.
+ std::optional<Error> addSymbol(const unsigned& line, const Symbol& s, VInt v) const;
+
+ /// Resolves an expression through either the built-in symbol map, or through the expression evaluator.
+ ExprEvalRes evalExpr(const QString& expr) const;
+
+ /// Set the supported directives for this assembler.
+ void setDirectives(const DirectiveVec& directives);
+
+protected:
+ /// Creates a set of LineTokens by tokenizing a line of source code.
+ QRegularExpression m_splitterRegex;
+ std::variant<Error, LineTokens> tokenize(const QString& line, const int sourceLine) const;
+
+ HandleDirectiveRes assembleDirective(const DirectiveArg& arg, bool& ok, bool skipEarlyDirectives = true) const;
+
+ /**
+ * @brief splitSymbolsFromLine
+ * @returns a pair consisting of a symbol and the the input @p line tokens where the symbol has been removed.
+ */
+ std::variant<Error, SymbolLinePair> splitSymbolsFromLine(const LineTokens& tokens, int sourceLine) const;
+
+ std::variant<Error, DirectiveLinePair> splitDirectivesFromLine(const LineTokens& tokens, int sourceLine) const;
+
+ /// Given an input set of tokens, splits away commented code from the tokens based on the comment delimiter, i.e.:
+ /// {"a", "b", "#", "c"} => {"a", "b"}
+ std::variant<Error, LineTokens> splitCommentFromLine(const LineTokens& tokens) const;
+
+ /// Returns the comment-delimiting character for this assembler.
+ virtual QChar commentDelimiter() const = 0;
+
+ /**
+ * @brief m_sectionBasePointers maintains the base position for the segments
+ * annoted by the Segment enum class.
+ */
+ std::map<Section, AInt> m_sectionBasePointers;
+ /**
+ * @brief m_currentSegment maintains the current segment where the assembler emits information.
+ * Marked mutable to allow for switching currently selected segment during assembling.
+ */
+ mutable Section m_currentSection;
+
+ /**
+ * @brief symbolMap maintains the symbols recorded during assembling. Marked mutable to allow for assembler
+ * directives to add symbols during assembling.
+ */
+ mutable SymbolMap m_symbolMap;
+
+ /**
+ * The set of supported assembler directives. A assembler can add directives through
+ * AssemblerBase::setDirectives.
+ */
+ DirectiveVec m_directives;
+ DirectiveMap m_directivesMap;
+ EarlyDirectives m_earlyDirectives;
+};
+
+} // namespace Assembler
+} // namespace Ripes

MODIFY
src/assembler/directive.h
src/assembler/directive.h
@@ -19,6 +19,8 @@ struct DirectiveArg {
const ProgramSection* section;
};
+/// An assembler directive represents a function which may be activated through the source code. This function will then
+/// perform some transformation to the current state of the assembler.
class Directive {
public:
using DirectiveHandler = std::function<HandleDirectiveRes(const AssemblerBase*, const DirectiveArg&)>;
@@ -32,7 +34,10 @@ public:
bool early() const { return m_early; }
private:
+ /// Name of this directive.
const QString m_directive;
+
+ /// Directive handler.
DirectiveHandler m_handler;
/**
@@ -42,8 +47,9 @@ private:
bool m_early;
};
-using DirectiveMap = std::map<QString, std::shared_ptr<Directive>>;
-using DirectiveVec = std::vector<std::shared_ptr<Directive>>;
+using DirectivePtr = std::shared_ptr<Directive>;
+using DirectiveMap = std::map<QString, DirectivePtr>;
+using DirectiveVec = std::vector<DirectivePtr>;
using EarlyDirectives = std::set<QString>;
} // namespace Assembler
} // namespace Ripes

Move AssemblerBase to separate compilation unit