21 Pass(
"MacroProcessor", debug)
24 m_conditional_executor = std::make_shared<ConditionalExecutor>(
this);
25 m_executors.emplace_back(std::make_shared<SymbolExecutor>(
this));
26 m_executors.emplace_back(m_conditional_executor);
27 m_executors.emplace_back(std::make_shared<FunctionExecutor>(
this));
54 assert(node.
constList().size() >= 2 &&
"Invalid macro, missing value");
56 const Node& first_node = node.
list()[0];
107 "Max recursion depth reached ({}). You most likely have a badly defined recursive macro calling itself without a proper exit condition",
113 bool has_created =
false;
116 while (i < node.
list().size())
118 const std::size_t pos = i;
121 bool added_begin =
false;
126 if ((!
m_macros.empty() && !
m_macros.back().empty() &&
m_macros.back().depth() < depth && !is_processing_namespace) ||
127 (!has_created && !is_processing_namespace) ||
128 (
m_macros.empty() && is_processing_namespace))
143 node.
list().erase(node.
constList().begin() +
static_cast<std::vector<Node>::difference_type
>(pos));
144 else if (!added_begin)
168 node.
list().erase(node.
constList().begin() +
static_cast<std::vector<Node>::difference_type
>(pos));
173 if (!
m_macros.empty() &&
m_macros.back().depth() == depth && !is_processing_namespace)
192 "Max macro processing depth reached ({}). You may have a macro trying to evaluate itself, try splitting your code in multiple nodes.",
198 if (executor->canHandle(node))
201 const bool applied = executor->applyMacro(node, depth);
213 const std::size_t argcount = node.
constList().size();
214 if (argcount != expected + 1)
217 "Interpreting a `{}'{} with {} arguments, expected {}.",
219 kind.empty() ? kind :
" " + kind,
227 const std::size_t argcount = node.
constList().size();
228 if (argcount < expected + 1)
231 "Interpreting a `{}'{} with {} arguments, expected at least {}.",
233 kind.empty() ? kind :
" " + kind,
244 if (macro !=
nullptr && macro->
constList().size() == 2)
250 const std::string& name = node.
list()[0].string();
251 const std::size_t argcount = node.
list().size() - 1;
259 else if (name ==
"=" && is_not_body)
266 else if (name ==
"!=" && is_not_body)
273 else if (name ==
"<" && is_not_body)
280 else if (name ==
">" && is_not_body)
287 else if (name ==
"<=" && is_not_body)
294 else if (name ==
">=" && is_not_body)
301 else if (name ==
"+" && is_not_body)
305 for (
auto& child : node.
list() | std::ranges::views::drop(1))
314 else if (name ==
"-" && is_not_body)
322 for (
auto& child : node.
list() | std::ranges::views::drop(2))
331 else if (name ==
"*" && is_not_body)
335 for (
auto& child : node.
list() | std::ranges::views::drop(1))
344 else if (name ==
"/" && is_not_body)
352 for (
auto& child : node.
list() | std::ranges::views::drop(2))
361 else if (name ==
"not" && is_not_body)
368 if (node.
list().size() < 3)
371 for (std::size_t i = 1, end = node.
list().size(); i < end; ++i)
380 if (node.
list().size() < 3)
383 for (std::size_t i = 1, end = node.
list().size(); i < end; ++i)
390 else if (name ==
"len")
392 if (node.
list().size() > 2)
393 throwMacroProcessingError(fmt::format(
"When expanding `len' inside a macro, got {} arguments, expected 1", argcount), node);
398 if (!lst.list().empty() && lst.list()[0] ==
getListNode())
405 else if (name ==
"empty?")
407 if (node.
list().size() > 2)
408 throwMacroProcessingError(fmt::format(
"When expanding `empty?' inside a macro, got {} arguments, expected 1", argcount), node);
412 if (!lst.list().empty() && lst.list()[0] ==
getListNode())
420 else if (name ==
"@")
429 const std::size_t size = sublist.
list().size();
430 std::size_t real_size = size;
431 long num_idx =
static_cast<long>(idx.
number());
442 if (num_idx >= 0 && std::cmp_less(num_idx, size))
443 output = sublist.
list()[
static_cast<std::size_t
>(num_idx)];
444 else if (
const auto c =
static_cast<long>(size) + num_idx; num_idx < 0 && std::cmp_less(c, size) && c >= 0)
445 output = sublist.
list()[
static_cast<std::size_t
>(c)];
454 else if (name ==
"head")
456 if (node.
list().size() > 2)
457 throwMacroProcessingError(fmt::format(
"When expanding `head' inside a macro, got {} arguments, expected 1", argcount), node);
471 else if (!sublist.
list().empty())
477 else if (name ==
"tail")
479 if (node.
list().size() > 2)
480 throwMacroProcessingError(fmt::format(
"When expanding `tail' inside a macro, got {} arguments, expected 1", argcount), node);
486 if (sublist.
list().size() > 1)
497 else if (!sublist.
list().empty())
512 if (node.
list().size() <= 2)
517 "When expanding `{}', expected the first argument to be a Symbol, got a {}: {}",
520 node.
list()[1].repr()),
523 std::string sym = node.
list()[1].string();
525 for (std::size_t i = 2, end = node.
list().size(); i < end; ++i)
533 sym += std::to_string(
static_cast<long int>(ev.
number()));
544 "When expanding `{}', expected either a Number, String or Symbol, got a {}: {}",
577 if (node.
list().size() != 2)
583 if (node.
list().size() != 2)
596 "When expanding `{}', got a {}. Can not un-define a macro without a valid name",
600 else if (name ==
"$type")
609 for (
auto& child : node.
list())
610 child.updateValueAndType(
evaluate(child, depth + 1, is_not_body));
623 if (node.
string() ==
"true")
647 for (
const auto& m_macro : std::ranges::reverse_view(
m_macros))
649 if (
const auto res = m_macro.has(name); res !=
nullptr)
660 for (
auto& m_macro : std::ranges::reverse_view(
m_macros))
662 if (m_macro.remove(name))
687 const std::size_t previous = i;
689 for (std::size_t block_idx = 1, end = lst.
constList().size(); block_idx < end; ++block_idx)
691 node.
constList().begin() +
static_cast<std::vector<Node>::difference_type
>(i + block_idx),
692 lst.
list()[block_idx]);
694 node.
list().erase(node.
constList().begin() +
static_cast<std::vector<Node>::difference_type
>(previous));
707 [&node](
const std::pair<std::string, Value>& element) ->
bool {
708 return node.
string() == element.first;
714 node.
string() ==
"list" ||
719 return std::ranges::all_of(node.
constList(), [
this](
const Node& child) {
720 return isConstEval(child);
742 const std::optional<CodeErrorContext> maybe_context = [
this]() -> std::optional<CodeErrorContext> {
Host the declaration of all the ArkScript builtins.
Executor for Conditional Macros.
Constants used by ArkScript.
ArkScript homemade exceptions.
The base class for all MacroExecutors.
Executor for List Macros.
Handles the macros and their expansion in ArkScript source code.
Executor for Symbol Macros.
void trace(const char *fmt, Args &&... args)
Write a trace level log using fmtlib.
void debug(const char *fmt, Args &&... args)
Write a debug level log using fmtlib.
void traceStart(std::string &&trace_name)
Node evaluate(Node &node, unsigned depth, bool is_not_body=false)
Evaluate only the macros.
void registerFuncDef(const Node &node)
Registers a function definition node.
const Node & ast() const noexcept override
Return the modified AST.
std::shared_ptr< MacroExecutor > m_conditional_executor
bool applyMacro(Node &node, unsigned depth)
Apply a macro on a given node.
static bool isBeginNode(const Node &node)
Check if a given node is a list node, and starts with a Begin.
static void removeBegin(Node &node, std::size_t i)
Remove a begin block added by a macro.
std::optional< Node > lookupDefinedFunction(const std::string &name) const
Return std::nullopt if the function isn't registered, otherwise return its node.
std::vector< Node > m_macros_being_applied
Stack of macros being applied. The last one is the current one we are working on.
const Node * findNearestMacro(const std::string &name) const
Find the nearest macro matching a given name.
Node m_ast
The modified AST.
void throwMacroProcessingError(const std::string &message, const Node &node) const
Throw a macro processing error.
bool isTruthy(const Node &node)
Check if a node can be evaluated to true.
std::unordered_map< std::string, Node > m_defined_functions
bool isConstEval(const Node &node) const
Check if a node can be evaluated at compile time.
std::vector< std::shared_ptr< MacroExecutor > > m_executors
void process(const Node &ast) override
Send the complete AST and work on it.
MacroProcessor(unsigned debug) noexcept
Construct a new Macro Processor object.
void processNode(Node &node, unsigned depth, bool is_processing_namespace=false)
Register macros in scopes and apply them as needed.
std::vector< MacroScope > m_macros
Handling macros in a scope fashion.
void checkMacroArgCountGe(const Node &node, std::size_t expected, const std::string &name, const std::string &kind="")
Check if the given node has at least the provided argument count, otherwise throws an error.
void checkMacroArgCountEq(const Node &node, std::size_t expected, const std::string &name, const std::string &kind="")
Check if the given node has exactly the provided argument count, otherwise throws an error.
void handleMacroNode(Node &node)
Registers macros based on their type, expand conditional macros.
void deleteNearestMacro(const std::string &name)
Find the nearest macro matching a given name and delete it.
A node of an Abstract Syntax Tree for ArkScript.
NodeType nodeType() const noexcept
Return the node type.
void setNodeType(NodeType type) noexcept
Set the Node Type object.
bool isListLike() const noexcept
Check if the node is a list like node.
const std::string & filename() const noexcept
Return the filename in which this node was created.
const std::string & string() const noexcept
Return the string held by the value (if the node type allows it)
void setPos(std::size_t line, std::size_t col) noexcept
Set the Position of the node in the text.
const std::vector< Node > & constList() const noexcept
Return the list of sub-nodes held by the node.
Keyword keyword() const noexcept
Return the keyword held by the value (if the node type allows it)
Namespace & arkNamespace() noexcept
Return the namespace held by the value (if the node type allows it)
std::string repr() const noexcept
Compute a representation of the node without any comments or additional sugar, colors,...
void setFilename(const std::string &filename) noexcept
Set the original Filename where the node was.
std::ostream & debugPrint(std::ostream &os) const noexcept
Print a node to an output stream with added type annotations.
std::size_t col() const noexcept
Get the column at which this node was created.
void push_back(const Node &node) noexcept
Every node has a list as well as a value so we can push_back on all node no matter their type.
void setString(const std::string &value) noexcept
Set the String object.
double number() const noexcept
Return the number held by the value (if the node type allows it)
void updateValueAndType(const Node &source) noexcept
Copy a node to the current one, while keeping the filename and position in the file.
std::size_t line() const noexcept
Get the line at which this node was created.
std::vector< Node > & list() noexcept
Return the list of sub-nodes held by the node.
An interface to describe compiler passes.
ARK_API const std::vector< std::pair< std::string, Value > > builtins
constexpr std::string_view AsIs
constexpr std::string_view Argcount
constexpr std::array< std::string_view, 24 > operators
constexpr std::string_view And
constexpr std::string_view Repr
constexpr std::string_view Or
constexpr std::string_view Symcat
constexpr std::string_view Undef
std::string typeToString(const Node &node) noexcept
const Node & getNilNode()
Keyword
The different keywords available.
const Node & getFalseNode()
const Node & getListNode()
const Node & getTrueNode()
constexpr std::size_t MaxMacroProcessingDepth
Controls the number of recursive calls to MacroProcessor::processNode.
CodeError thrown by the compiler (parser, macro processor, optimizer, and compiler itself)
std::shared_ptr< Node > ast