ArkScript
A small, lisp-inspired, functional scripting language
Processor.cpp
Go to the documentation of this file.
2
3#include <utility>
4#include <algorithm>
5#include <cassert>
6#include <ranges>
7#include <sstream>
8#include <fmt/core.h>
9
10#include <Ark/Constants.hpp>
17
18namespace Ark::internal
19{
20 MacroProcessor::MacroProcessor(const unsigned debug) noexcept :
21 Pass("MacroProcessor", debug), m_genned_sym(0)
22 {
23 // create executors pipeline
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));
28 }
29
31 {
32 m_logger.debug("Processing macros...");
33 m_logger.traceStart("process");
34
35 // to be able to modify it
36 m_ast = ast;
38
40 m_logger.debug("AST after processing macros");
42 m_ast.debugPrint(std::cout) << '\n';
43 }
44
45 const Node& MacroProcessor::ast() const noexcept
46 {
47 return m_ast;
48 }
49
50 void MacroProcessor::handleMacroNode(Node& node, const unsigned depth)
51 {
52 // a macro needs at least 2 nodes, name + value is the minimal form
53 // this is guaranteed by the parser
54 assert(node.constList().size() >= 2 && "Invalid macro, missing value");
55
56 const Node& first_node = node.list()[0];
57
58 // (macro name value)
59 if (node.constList().size() == 2)
60 {
61 assert(first_node.nodeType() == NodeType::Symbol && "Can not define a macro without a symbol");
62 // processNode is needed here to correctly expand macros when we have something like
63 // (macro arg_bloc (__suffix-dup arg length)) /
64 // (macro all_args (__replace_placeholders [arg_bloc] ...args)) <--------------*
65 // otherwise, 'arg_bloc' isn't expanded here, and we get in trouble
66 processNode(node.list()[1], depth + 1);
67 applyMacro(node.list()[1], depth + 1);
68 node.list()[1] = evaluate(node.list()[1], depth + 1, true);
69 m_macros.back().add(first_node.string(), node);
70 }
71 // (macro name (args) body)
72 else if (node.constList().size() == 3 && first_node.nodeType() == NodeType::Symbol)
73 {
74 assert(node.constList()[1].nodeType() == NodeType::List && "Invalid macro argument's list");
75 m_macros.back().add(first_node.string(), node);
76 }
77 // in case we had a conditional, we need to evaluate and expand it
78 else if (m_conditional_executor->canHandle(node))
79 m_conditional_executor->applyMacro(node, depth + 1);
80 }
81
82 // todo find a better way to do this
84 {
85 if (node.nodeType() == NodeType::List && node.constList().size() == 3 && node.constList()[0].nodeType() == NodeType::Keyword)
86 {
87 const Keyword kw = node.constList()[0].keyword();
88 // checking for function definition, which can occur only inside an assignment node
89 if (kw != Keyword::Let && kw != Keyword::Mut && kw != Keyword::Set)
90 return;
91
92 const Node inner = node.constList()[2];
93 if (inner.nodeType() != NodeType::List)
94 return;
95
96 if (!inner.constList().empty() && inner.constList()[0].nodeType() == NodeType::Keyword && inner.constList()[0].keyword() == Keyword::Fun)
97 {
98 const Node symbol = node.constList()[1];
99 if (symbol.nodeType() == NodeType::Symbol)
100 m_defined_functions.emplace(symbol.string(), inner.constList()[1]);
101 else
102 throwMacroProcessingError(fmt::format("Can not use a {} to define a variable", typeToString(symbol)), symbol);
103 }
104 }
105 }
106
107 void MacroProcessor::processNode(Node& node, unsigned depth, const bool is_processing_namespace)
108 {
109 if (depth >= MaxMacroProcessingDepth)
111 fmt::format(
112 "Max recursion depth reached ({}). You most likely have a badly defined recursive macro calling itself without a proper exit condition",
114 node);
115
116 if (node.nodeType() == NodeType::List)
117 {
118 bool has_created = false;
119 std::size_t i = 0;
120 while (i < node.list().size())
121 {
122 const std::size_t pos = i;
123 Node& child = node.list()[pos];
124 const bool had_begin = isBeginNode(child);
125 bool added_begin = false;
126
127 if (child.nodeType() == NodeType::Macro)
128 {
129 // create a scope only if needed
130 if ((!m_macros.empty() && !m_macros.back().empty() && m_macros.back().depth() < depth && !is_processing_namespace) ||
131 (!has_created && !is_processing_namespace) ||
132 (m_macros.empty() && is_processing_namespace))
133 {
134 has_created = true;
135 m_macros.emplace_back(depth);
136 }
137
138 handleMacroNode(child, depth);
139 if (child.nodeType() != NodeType::Macro)
140 evaluate(child, depth);
141 added_begin = isBeginNode(child) && !had_begin;
142 }
143 else // running on non-macros
144 {
145 applyMacro(child, 0);
146 added_begin = isBeginNode(child) && !had_begin;
147
148 if (child.nodeType() == NodeType::Unused)
149 node.list().erase(node.constList().begin() + static_cast<std::vector<Node>::difference_type>(pos));
150 else if (!added_begin)
151 // Go forward only if it isn't a macro, because we delete macros
152 // while running on the AST. Also, applying a macro can result in
153 // nodes being marked unused, and delete them immediately. When
154 // that happens, we can't increment i, otherwise we delete a node,
155 // advance, resulting in a node being skipped!
156 ++i;
157
158 // process subnodes if any
159 if (node.nodeType() == NodeType::List && pos < node.constList().size())
160 {
161 processNode(child, depth + 1);
162 // needed if we created a function node from a macro
163 registerFuncDef(child);
164 }
165 }
166
167 if (pos < node.constList().size())
168 {
169 // if we now have a surrounding (begin ...) and didn't have one before, remove it
170 if (added_begin)
171 removeBegin(node, pos);
172 // if there is an unused node or a leftover macro need, we need to get rid of it in the final ast
173 else if (child.nodeType() == NodeType::Macro || child.nodeType() == NodeType::Unused)
174 node.list().erase(node.constList().begin() + static_cast<std::vector<Node>::difference_type>(pos));
175 }
176 }
177
178 // delete a scope only if needed
179 if (!m_macros.empty() && m_macros.back().depth() == depth && !is_processing_namespace)
180 m_macros.pop_back();
181 }
182 else if (node.nodeType() == NodeType::Namespace)
183 {
184 Node& namespace_ast = *node.arkNamespace().ast;
185 // We have to use depth - 1 because it was incremented previously, as a namespace node
186 // must be in a list node. Then depth - 1 is safe as depth is at least 1.
187 // Using a decreased value of depth ensures that macros are stored in the correct scope,
188 // and not deleted when the namespace traversal ends.
189 processNode(namespace_ast, depth - 1, /* is_processing_namespace= */ true);
190 }
191 }
192
193 bool MacroProcessor::applyMacro(Node& node, const unsigned depth)
194 {
195 if (depth > MaxMacroProcessingDepth)
197 fmt::format(
198 "Max macro processing depth reached ({}). You may have a macro trying to evaluate itself, try splitting your code in multiple nodes.",
200 node);
201
202 for (const auto& executor : m_executors)
203 {
204 if (executor->canHandle(node))
205 {
206 m_macros_being_applied.push_back(executor->macroNode(node));
207 const bool applied = executor->applyMacro(node, depth);
208 m_macros_being_applied.pop_back();
209
210 if (applied)
211 return true;
212 }
213 }
214 return false;
215 }
216
217 void MacroProcessor::checkMacroArgCountEq(const Node& node, std::size_t expected, const std::string& name, const bool is_expansion, const std::string& kind) const
218 {
219 const std::size_t argcount = node.constList().size();
220 if (argcount != expected + 1)
221 {
222 if (is_expansion)
224 fmt::format(
225 "When expanding `{}' inside a macro, got {} argument{}, expected {}",
226 name,
227 argcount - 1,
228 argcount > 2 ? "s" : "",
229 expected),
230 node);
231 else
233 fmt::format(
234 "Interpreting a `{}'{} with {} argument{}, expected {}.",
235 name,
236 kind.empty() ? kind : " " + kind,
237 argcount - 1,
238 argcount > 2 ? "s" : "",
239 expected),
240 node);
241 }
242 }
243
244 void MacroProcessor::checkMacroArgCountGe(const Node& node, std::size_t expected, const std::string& name, const std::string& kind) const
245 {
246 const std::size_t argcount = node.constList().size();
247 if (argcount < expected + 1)
249 fmt::format(
250 "Interpreting a `{}'{} with {} argument{}, expected at least {}.",
251 name,
252 kind.empty() ? kind : " " + kind,
253 argcount - 1,
254 argcount > 2 ? "s" : "",
255 expected),
256 node);
257 }
258
259 Node MacroProcessor::evaluate(Node& node, const unsigned depth, const bool is_not_body)
260 {
261 if (node.nodeType() == NodeType::Symbol)
262 {
263 const Node* macro = findNearestMacro(node.string());
264 if (macro != nullptr && macro->constList().size() == 2)
265 return macro->constList()[1];
266 return node;
267 }
268 if (node.nodeType() == NodeType::List && !node.constList().empty() && node.list()[0].nodeType() == NodeType::Symbol)
269 {
270 const std::string& name = node.list()[0].string();
271 const std::size_t argcount = node.list().size() - 1;
272
273 if (const Node* macro = findNearestMacro(name); macro != nullptr)
274 {
275 applyMacro(node.list()[0], depth + 1);
276 if (node.list()[0].nodeType() == NodeType::Unused)
277 node.list().erase(node.constList().begin());
278 }
279 else if (name == "=" && is_not_body)
280 {
281 checkMacroArgCountEq(node, 2, "=", /* is_expansion= */ false, "condition");
282 const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
283 const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
284 return (one == two) ? getTrueNode() : getFalseNode();
285 }
286 else if (name == "!=" && is_not_body)
287 {
288 checkMacroArgCountEq(node, 2, "!=", /* is_expansion= */ false, "condition");
289 const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
290 const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
291 return (one != two) ? getTrueNode() : getFalseNode();
292 }
293 else if (name == "<" && is_not_body)
294 {
295 checkMacroArgCountEq(node, 2, "<", /* is_expansion= */ false, "condition");
296 const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
297 const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
298 return (one < two) ? getTrueNode() : getFalseNode();
299 }
300 else if (name == ">" && is_not_body)
301 {
302 checkMacroArgCountEq(node, 2, ">", /* is_expansion= */ false, "condition");
303 const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
304 const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
305 return !(one < two) && (one != two) ? getTrueNode() : getFalseNode();
306 }
307 else if (name == "<=" && is_not_body)
308 {
309 checkMacroArgCountEq(node, 2, "<=", /* is_expansion= */ false, "condition");
310 const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
311 const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
312 return one < two || one == two ? getTrueNode() : getFalseNode();
313 }
314 else if (name == ">=" && is_not_body)
315 {
316 checkMacroArgCountEq(node, 2, ">=", /* is_expansion= */ false, "condition");
317 const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
318 const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
319 return !(one < two) ? getTrueNode() : getFalseNode();
320 }
321 else if (name == "+" && is_not_body)
322 {
323 checkMacroArgCountGe(node, 2, "+", "operator");
324 double v = 0.0;
325 for (auto& child : node.list() | std::ranges::views::drop(1))
326 {
327 Node ev = evaluate(child, depth + 1, is_not_body);
328 if (ev.nodeType() != NodeType::Number)
329 return node;
330 v += ev.number();
331 }
332 return Node(v);
333 }
334 else if (name == "-" && is_not_body)
335 {
336 checkMacroArgCountGe(node, 2, "-", "operator");
337 const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
338 if (one.nodeType() != NodeType::Number)
339 return node;
340
341 double v = one.number();
342 for (auto& child : node.list() | std::ranges::views::drop(2))
343 {
344 Node ev = evaluate(child, depth + 1, is_not_body);
345 if (ev.nodeType() != NodeType::Number)
346 return node;
347 v -= ev.number();
348 }
349 return Node(v);
350 }
351 else if (name == "*" && is_not_body)
352 {
353 checkMacroArgCountGe(node, 2, "*", "operator");
354 double v = 1.0;
355 for (auto& child : node.list() | std::ranges::views::drop(1))
356 {
357 Node ev = evaluate(child, depth + 1, is_not_body);
358 if (ev.nodeType() != NodeType::Number)
359 return node;
360 v *= ev.number();
361 }
362 return Node(v);
363 }
364 else if (name == "/" && is_not_body)
365 {
366 checkMacroArgCountGe(node, 2, "/", "operator");
367 const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
368 if (one.nodeType() != NodeType::Number)
369 return node;
370
371 double v = one.number();
372 for (auto& child : node.list() | std::ranges::views::drop(2))
373 {
374 Node ev = evaluate(child, depth + 1, is_not_body);
375 if (ev.nodeType() != NodeType::Number)
376 return node;
377 v /= ev.number();
378 }
379 return Node(v);
380 }
381 else if (name == "not" && is_not_body)
382 {
383 checkMacroArgCountEq(node, 1, "not", /* is_expansion= */ false, "condition");
384 return (!isTruthy(evaluate(node.list()[1], depth + 1, is_not_body))) ? getTrueNode() : getFalseNode();
385 }
386 else if (name == Language::And && is_not_body)
387 {
388 if (node.list().size() < 3)
389 throwMacroProcessingError(fmt::format("Interpreting a `{}' chain with {} arguments, expected at least 2.", Language::And, argcount), node);
390
391 for (std::size_t i = 1, end = node.list().size(); i < end; ++i)
392 {
393 if (!isTruthy(evaluate(node.list()[i], depth + 1, is_not_body)))
394 return getFalseNode();
395 }
396 return getTrueNode();
397 }
398 else if (name == Language::Or && is_not_body)
399 {
400 if (node.list().size() < 3)
401 throwMacroProcessingError(fmt::format("Interpreting an `{}' chain with {} arguments, expected at least 2.", Language::Or, argcount), node);
402
403 for (std::size_t i = 1, end = node.list().size(); i < end; ++i)
404 {
405 if (isTruthy(evaluate(node.list()[i], depth + 1, is_not_body)))
406 return getTrueNode();
407 }
408 return getFalseNode();
409 }
410 else if (name == "$len")
411 {
412 checkMacroArgCountEq(node, 1, "$len", true);
413 Node& lst = node.list()[1];
414 checkMacroTypeError("$len", "node", NodeType::List, lst);
415
416 if (!lst.list().empty() && lst.list()[0] == getListNode())
417 node.updateValueAndType(Node(static_cast<long>(lst.list().size()) - 1));
418 else
419 node.updateValueAndType(Node(static_cast<long>(lst.list().size())));
420 }
421 else if (name == "$empty?")
422 {
423 checkMacroArgCountEq(node, 1, "$empty?", true);
424
425 if (Node& lst = node.list()[1]; lst.nodeType() == NodeType::List)
426 {
427 // only apply len at compile time if we can
428 if (!lst.list().empty() && lst.list()[0] == getListNode())
429 node.updateValueAndType(lst.list().size() - 1 == 0 ? getTrueNode() : getFalseNode());
430 else
431 node.updateValueAndType(lst.list().empty() ? getTrueNode() : getFalseNode());
432 }
433 else if (lst == getNilNode())
435 }
436 else if (name == "$at")
437 {
438 checkMacroArgCountEq(node, 2, "$at");
439
440 Node sublist = evaluate(node.list()[1], depth + 1, is_not_body);
441 const Node idx = evaluate(node.list()[2], depth + 1, is_not_body);
442
443 checkMacroTypeError("$at", "list", NodeType::List, sublist);
444 checkMacroTypeError("$at", "index", NodeType::Number, idx);
445
446 const std::size_t size = sublist.list().size();
447 std::size_t real_size = size;
448 long num_idx = static_cast<long>(idx.number());
449
450 // if the first node is the function call to "list", don't count it
451 if (size > 0 && sublist.list()[0] == getListNode())
452 {
453 real_size--;
454 if (num_idx >= 0)
455 ++num_idx;
456 }
457
458 Node output;
459 if (num_idx >= 0 && std::cmp_less(num_idx, size))
460 output = sublist.list()[static_cast<std::size_t>(num_idx)];
461 else if (const auto c = static_cast<long>(size) + num_idx; num_idx < 0 && std::cmp_less(c, size) && c >= 0)
462 output = sublist.list()[static_cast<std::size_t>(c)];
463 else
464 throwMacroProcessingError(fmt::format("Index ({}) out of range (list size: {})", num_idx, real_size), node);
465
466 output.setPositionFrom(node);
467 return output;
468 }
469 else if (name == "$head")
470 {
471 checkMacroArgCountEq(node, 1, "$head", true);
472 Node sublist = node.list()[1];
473 checkMacroTypeError("$head", "node", NodeType::List, sublist);
474
475 if (!sublist.constList().empty() && sublist.constList()[0] == getListNode())
476 {
477 if (sublist.constList().size() > 1)
478 {
479 const Node sublistCopy = sublist.constList()[1];
480 node.updateValueAndType(sublistCopy);
481 }
482 else
484 }
485 else if (!sublist.list().empty())
486 node.updateValueAndType(sublist.constList()[0]);
487 else
489 }
490 else if (name == "$tail")
491 {
492 checkMacroArgCountEq(node, 1, "$tail", true);
493 Node sublist = node.list()[1];
494 checkMacroTypeError("$tail", "node", NodeType::List, sublist);
495
496 if (!sublist.list().empty() && sublist.list()[0] == getListNode())
497 {
498 if (sublist.list().size() > 1)
499 {
500 sublist.list().erase(sublist.constList().begin() + 1);
501 node.updateValueAndType(sublist);
502 }
503 else
504 {
506 node.push_back(getListNode());
507 }
508 }
509 else if (!sublist.list().empty())
510 {
511 sublist.list().erase(sublist.constList().begin());
512 sublist.list().insert(sublist.list().begin(), getListNode());
513 node.updateValueAndType(sublist);
514 }
515 else
516 {
518 node.push_back(getListNode());
519 }
520 }
521 else if (name == "$gensym")
522 {
523 checkMacroArgCountEq(node, 0, "$gensym", true);
524 node.updateValueAndType(Node(NodeType::Symbol, fmt::format("#gensym-{}", m_genned_sym)));
525 ++m_genned_sym;
526 }
527 else if (name == Language::Symcat)
528 {
529 if (node.list().size() <= 2)
530 throwMacroProcessingError(fmt::format("When expanding `{}', expected at least 2 arguments, got {} arguments", Language::Symcat, argcount), node);
531 if (node.list()[1].nodeType() != NodeType::Symbol && node.list()[1].nodeType() != NodeType::String)
532 checkMacroTypeError(Language::Symcat.data(), "symbol", NodeType::Symbol, node.list()[1]);
533
534 std::string sym = node.list()[1].string();
535
536 for (std::size_t i = 2, end = node.list().size(); i < end; ++i)
537 {
538 const Node ev = evaluate(node.list()[i], depth + 1, /* is_not_body */ true);
539
540 switch (ev.nodeType())
541 {
542 case NodeType::Number:
543 // we don't want '.' in identifiers
544 sym += std::to_string(static_cast<long int>(ev.number()));
545 break;
546
547 case NodeType::String:
548 case NodeType::Symbol:
549 sym += ev.string();
550 break;
551
552 default:
554 fmt::format(
555 "When expanding `{}', expected either a Number, String or Symbol, got a {}: {}",
557 typeToString(ev),
558 ev.repr()),
559 ev);
560 }
561 }
562
564 node.setString(sym);
565 }
566 else if (name == Language::Argcount)
567 {
568 checkMacroArgCountEq(node, 1, Language::Argcount.data(), true);
569
570 const Node sym = node.constList()[1];
571 if (sym.nodeType() == NodeType::Symbol)
572 {
573 if (const auto maybe_func = lookupDefinedFunction(sym.string()); maybe_func.has_value())
574 node.updateValueAndType(Node(static_cast<long>(maybe_func->constList().size())));
575 else
576 throwMacroProcessingError(fmt::format("When expanding `{}', expected a known function name, got unbound variable {}", Language::Argcount, sym.string()), sym);
577 }
578 else if (sym.nodeType() == NodeType::List && sym.constList().size() == 3 && sym.constList()[0].nodeType() == NodeType::Keyword && sym.constList()[0].keyword() == Keyword::Fun)
579 node.updateValueAndType(Node(static_cast<long>(sym.constList()[1].constList().size())));
580 else
581 throwMacroProcessingError(fmt::format("When trying to apply `{}', got a {} instead of a Symbol or Function", Language::Argcount, typeToString(sym)), sym);
582 }
583 else if (name == Language::Repr)
584 {
585 checkMacroArgCountEq(node, 1, Language::Repr.data(), true);
586
587 const Node arg = node.constList()[1];
588 if (arg.nodeType() == NodeType::Symbol)
589 {
590 if (const Node* arg_as_macro = findNearestMacro(arg.string()); arg_as_macro != nullptr)
591 node.updateValueAndType(Node(NodeType::String, arg_as_macro->repr()));
592 else
594 }
595 else
597 }
598 else if (name == Language::AsIs)
599 {
600 if (node.list().size() != 2)
601 throwMacroProcessingError(fmt::format("When expanding `{}', expected one argument, got {} arguments", Language::AsIs, argcount), node);
602 return node.constList()[1];
603 }
604 else if (name == Language::Undef)
605 {
606 if (node.list().size() != 2)
607 throwMacroProcessingError(fmt::format("When expanding `{}', expected one argument, got {} arguments", Language::Undef, argcount), node);
608
609 const Node sym = node.constList()[1];
610 if (sym.nodeType() == NodeType::Symbol)
611 {
614 return node;
615 }
616
618 fmt::format(
619 "When expanding `{}', got a {}. Can not un-define a macro without a valid name",
621 sym);
622 }
623 else if (name == Language::Type)
624 {
625 if (node.list().size() != 2)
626 throwMacroProcessingError(fmt::format("When expanding `{}', expected one argument, got {} arguments", Language::Type, argcount), node);
627
628 const Node arg = node.constList()[1];
630 }
631 }
632
633 if (node.nodeType() == NodeType::List && !node.constList().empty())
634 {
635 for (auto& child : node.list())
636 child.updateValueAndType(evaluate(child, depth + 1, is_not_body));
637 }
638
639 if (node.nodeType() == NodeType::Spread)
640 throwMacroProcessingError(fmt::format("Found an unevaluated spread: `{}'", node.string()), node);
641
642 return node;
643 }
644
645 bool MacroProcessor::isTruthy(const Node& node) const
646 {
647 if (node.nodeType() == NodeType::Symbol)
648 {
649 if (node.string() == "true")
650 return true;
651 if (node.string() == "false" || node.string() == "nil")
652 return false;
653 }
654 else if ((node.nodeType() == NodeType::Number && node.number() != 0.0) || (node.nodeType() == NodeType::String && !node.string().empty()))
655 return true;
656 else if (node.nodeType() == NodeType::Spread)
657 throwMacroProcessingError("Can not determine the truth value of a spread symbol", node);
658 return false;
659 }
660
661 std::optional<Node> MacroProcessor::lookupDefinedFunction(const std::string& name) const
662 {
663 if (m_defined_functions.contains(name))
664 return m_defined_functions.at(name);
665 return std::nullopt;
666 }
667
668 const Node* MacroProcessor::findNearestMacro(const std::string& name) const
669 {
670 if (m_macros.empty())
671 return nullptr;
672
673 for (const auto& m_macro : std::ranges::reverse_view(m_macros))
674 {
675 if (const auto res = m_macro.has(name); res != nullptr)
676 return res;
677 }
678 return nullptr;
679 }
680
681 void MacroProcessor::deleteNearestMacro(const std::string& name)
682 {
683 if (m_macros.empty())
684 return;
685
686 for (auto& m_macro : std::ranges::reverse_view(m_macros))
687 {
688 if (m_macro.remove(name))
689 {
690 // stop right here because we found one matching macro
691 return;
692 }
693 }
694 }
695
697 {
698 return node.nodeType() == NodeType::List &&
699 !node.constList().empty() &&
700 node.constList()[0].nodeType() == NodeType::Keyword &&
701 node.constList()[0].keyword() == Keyword::Begin;
702 }
703
704 void MacroProcessor::removeBegin(Node& node, const std::size_t i)
705 {
706 if (node.isListLike() && node.list()[i].nodeType() == NodeType::List && !node.list()[i].list().empty())
707 {
708 Node lst = node.constList()[i];
709 const Node first = lst.constList()[0];
710
711 if (first.nodeType() == NodeType::Keyword && first.keyword() == Keyword::Begin)
712 {
713 const std::size_t previous = i;
714
715 for (std::size_t block_idx = 1, end = lst.constList().size(); block_idx < end; ++block_idx)
716 node.list().insert(
717 node.constList().begin() + static_cast<std::vector<Node>::difference_type>(i + block_idx),
718 lst.list()[block_idx]);
719
720 node.list().erase(node.constList().begin() + static_cast<std::vector<Node>::difference_type>(previous));
721 }
722 }
723 }
724
725 void MacroProcessor::throwMacroProcessingError(const std::string& message, const Node& node) const
726 {
727 const std::optional<CodeErrorContext> maybe_context = [this]() -> std::optional<CodeErrorContext> {
728 if (!m_macros_being_applied.empty())
729 {
730 const Node& origin = m_macros_being_applied.front();
731 return CodeErrorContext(
732 origin.filename(),
733 origin.position(),
734 /* from_macro_expansion= */ true);
735 }
736 return std::nullopt;
737 }();
738
739 throw CodeError(message, CodeErrorContext(node.filename(), node.position()), maybe_context);
740 }
741
742 void MacroProcessor::checkMacroTypeError(const std::string& macro, const std::string& arg, const NodeType expected, const Node& actual) const
743 {
744 if (actual.nodeType() != expected)
746 fmt::format(
747 "When expanding `{}', expected '{}' to be a {}, got a {}: {}",
748 macro,
749 arg,
750 std::string(nodeTypes[static_cast<std::size_t>(expected)]),
751 typeToString(actual),
752 actual.repr()),
753 actual);
754 }
755}
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 debug(const Logger::MessageAndLocation &data, Args &&... args)
Write a debug level log using fmtlib.
Definition Logger.hpp:96
bool shouldDebug() const
Definition Logger.hpp:54
void traceStart(std::string &&trace_name)
Definition Logger.hpp:109
void checkMacroArgCountGe(const Node &node, std::size_t expected, const std::string &name, const std::string &kind="") const
Check if the given node has at least the provided argument count, otherwise throws an error.
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.
Definition Processor.cpp:83
std::shared_ptr< MacroExecutor > m_conditional_executor
Definition Processor.hpp:61
const Node & ast() const noexcept
Return the modified AST.
Definition Processor.cpp:45
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.
Definition Processor.hpp:60
const Node * findNearestMacro(const std::string &name) const
Find the nearest macro matching a given name.
Node m_ast
The modified AST.
Definition Processor.hpp:58
void throwMacroProcessingError(const std::string &message, const Node &node) const
Throw a macro processing error.
void checkMacroTypeError(const std::string &macro, const std::string &arg, NodeType expected, const Node &actual) const
void handleMacroNode(Node &node, unsigned depth)
Registers macros based on their type, expand conditional macros.
Definition Processor.cpp:50
std::unordered_map< std::string, Node > m_defined_functions
Definition Processor.hpp:63
std::vector< std::shared_ptr< MacroExecutor > > m_executors
Definition Processor.hpp:62
void checkMacroArgCountEq(const Node &node, std::size_t expected, const std::string &name, bool is_expansion=false, const std::string &kind="") const
Check if the given node has exactly the provided argument count, otherwise throws an error.
MacroProcessor(unsigned debug) noexcept
Construct a new Macro Processor object.
Definition Processor.cpp:20
void processNode(Node &node, unsigned depth, bool is_processing_namespace=false)
Register macros in scopes and apply them as needed.
bool isTruthy(const Node &node) const
Check if a node can be evaluated to true.
void process(const Node &ast)
Send the complete AST and work on it.
Definition Processor.cpp:30
std::vector< MacroScope > m_macros
Handling macros in a scope fashion.
Definition Processor.hpp:59
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.
Definition Node.hpp:32
NodeType nodeType() const noexcept
Return the node type.
Definition Node.cpp:78
void setNodeType(NodeType type) noexcept
Set the Node Type object.
Definition Node.cpp:107
bool isListLike() const noexcept
Check if the node is a list like node.
Definition Node.cpp:83
const std::string & filename() const noexcept
Return the filename in which this node was created.
Definition Node.cpp:174
const std::string & string() const noexcept
Return the string held by the value (if the node type allows it)
Definition Node.cpp:38
const std::vector< Node > & constList() const noexcept
Return the list of sub-nodes held by the node.
Definition Node.cpp:73
Keyword keyword() const noexcept
Return the keyword held by the value (if the node type allows it)
Definition Node.cpp:48
Namespace & arkNamespace() noexcept
Return the namespace held by the value (if the node type allows it)
Definition Node.cpp:53
std::string repr() const noexcept
Compute a representation of the node without any comments or additional sugar, colors,...
Definition Node.cpp:189
void setPositionFrom(const Node &source) noexcept
Position the current node at a given span in a file.
Definition Node.cpp:122
std::ostream & debugPrint(std::ostream &os) const noexcept
Print a node to an output stream with added type annotations.
Definition Node.cpp:297
FileSpan position() const noexcept
Get the span of the node (start and end)
Definition Node.cpp:169
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.
Definition Node.cpp:63
void setString(const std::string &value) noexcept
Set the String object.
Definition Node.cpp:117
double number() const noexcept
Return the number held by the value (if the node type allows it)
Definition Node.cpp:43
void updateValueAndType(const Node &source) noexcept
Copy a node to the current one, while keeping the filename and position in the file.
Definition Node.cpp:101
std::vector< Node > & list() noexcept
Return the list of sub-nodes held by the node.
Definition Node.cpp:68
An interface to describe compiler passes.
Definition Pass.hpp:24
constexpr std::string_view AsIs
Definition Common.hpp:146
constexpr std::string_view Type
Definition Common.hpp:147
constexpr std::string_view Argcount
Definition Common.hpp:144
constexpr std::string_view And
Definition Common.hpp:137
constexpr std::string_view Repr
Definition Common.hpp:145
constexpr std::string_view Or
Definition Common.hpp:138
constexpr std::string_view Symcat
Definition Common.hpp:143
constexpr std::string_view Undef
Definition Common.hpp:142
ARK_ALWAYS_INLINE std::string typeToString(const Node &node) noexcept
Definition Node.hpp:280
NodeType
The different node types available.
Definition Common.hpp:44
const Node & getNilNode()
Definition Node.cpp:424
Keyword
The different keywords available.
Definition Common.hpp:79
constexpr std::array< std::string_view, 13 > nodeTypes
Node types as string, in the same order as the enum NodeType.
Definition Common.hpp:61
const Node & getFalseNode()
Definition Node.cpp:418
const Node & getListNode()
Definition Node.cpp:430
const Node & getTrueNode()
Definition Node.cpp:412
constexpr std::size_t MaxMacroProcessingDepth
Controls the number of recursive calls to MacroProcessor::processNode.
Definition Constants.hpp:84
std::string to_string(const Ark::ValueType type) noexcept
Definition Value.hpp:235
CodeError thrown by the compiler (parser, macro processor, optimizer, and compiler itself)
std::shared_ptr< Node > ast
Definition Namespace.hpp:18