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