ArkScript
A small, fast, functional and scripting language for video games
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>
11#include <Ark/Exceptions.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 guaranted by the parser
54 assert(node.constList().size() >= 2 && "Invalid macro, missing value");
55
56 const Node& first_node = node.list()[0];
57
58 // ($ 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.list()[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 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, 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 if (executor->applyMacro(node, depth))
201 return true;
202 }
203 }
204 return false;
205 }
206
207 void MacroProcessor::checkMacroArgCountEq(const Node& node, std::size_t expected, const std::string& name, const std::string& kind)
208 {
209 const std::size_t argcount = node.constList().size();
210 if (argcount != expected + 1)
212 fmt::format(
213 "Interpreting a `{}'{} with {} arguments, expected {}.",
214 name,
215 kind.empty() ? kind : " " + kind,
216 argcount,
217 expected),
218 node);
219 }
220
221 void MacroProcessor::checkMacroArgCountGe(const Node& node, std::size_t expected, const std::string& name, const std::string& kind)
222 {
223 const std::size_t argcount = node.constList().size();
224 if (argcount < expected + 1)
226 fmt::format(
227 "Interpreting a `{}'{} with {} arguments, expected at least {}.",
228 name,
229 kind.empty() ? kind : " " + kind,
230 argcount,
231 expected),
232 node);
233 }
234
235 Node MacroProcessor::evaluate(Node& node, const unsigned depth, const bool is_not_body)
236 {
237 if (node.nodeType() == NodeType::Symbol)
238 {
239 const Node* macro = findNearestMacro(node.string());
240 if (macro != nullptr && macro->constList().size() == 2)
241 return macro->constList()[1];
242 return node;
243 }
244 if (node.nodeType() == NodeType::List && !node.constList().empty() && node.list()[0].nodeType() == NodeType::Symbol)
245 {
246 const std::string& name = node.list()[0].string();
247 const std::size_t argcount = node.list().size() - 1;
248
249 if (const Node* macro = findNearestMacro(name); macro != nullptr)
250 {
251 applyMacro(node.list()[0], depth + 1);
252 if (node.list()[0].nodeType() == NodeType::Unused)
253 node.list().erase(node.constList().begin());
254 }
255 else if (name == "=" && is_not_body)
256 {
257 checkMacroArgCountEq(node, 2, "=", "condition");
258 const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
259 const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
260 return (one == two) ? getTrueNode() : getFalseNode();
261 }
262 else if (name == "!=" && is_not_body)
263 {
264 checkMacroArgCountEq(node, 2, "!=", "condition");
265 const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
266 const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
267 return (one != two) ? getTrueNode() : getFalseNode();
268 }
269 else if (name == "<" && is_not_body)
270 {
271 checkMacroArgCountEq(node, 2, "<", "condition");
272 const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
273 const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
274 return (one < two) ? getTrueNode() : getFalseNode();
275 }
276 else if (name == ">" && is_not_body)
277 {
278 checkMacroArgCountEq(node, 2, ">", "condition");
279 const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
280 const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
281 return !(one < two) && (one != two) ? getTrueNode() : getFalseNode();
282 }
283 else if (name == "<=" && is_not_body)
284 {
285 checkMacroArgCountEq(node, 2, "<=", "condition");
286 const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
287 const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
288 return one < two || one == two ? getTrueNode() : getFalseNode();
289 }
290 else if (name == ">=" && is_not_body)
291 {
292 checkMacroArgCountEq(node, 2, ">=", "condition");
293 const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
294 const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
295 return !(one < two) ? getTrueNode() : getFalseNode();
296 }
297 else if (name == "+" && is_not_body)
298 {
299 checkMacroArgCountGe(node, 2, "+", "operator");
300 double v = 0.0;
301 for (auto& child : node.list() | std::ranges::views::drop(1))
302 {
303 Node ev = evaluate(child, depth + 1, is_not_body);
304 if (ev.nodeType() != NodeType::Number)
305 return node;
306 v += ev.number();
307 }
308 return Node(v);
309 }
310 else if (name == "-" && is_not_body)
311 {
312 checkMacroArgCountGe(node, 2, "-", "operator");
313 const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
314 if (one.nodeType() != NodeType::Number)
315 return node;
316
317 double v = one.number();
318 for (auto& child : node.list() | std::ranges::views::drop(2))
319 {
320 Node ev = evaluate(child, depth + 1, is_not_body);
321 if (ev.nodeType() != NodeType::Number)
322 return node;
323 v -= ev.number();
324 }
325 return Node(v);
326 }
327 else if (name == "*" && is_not_body)
328 {
329 checkMacroArgCountGe(node, 2, "*", "operator");
330 double v = 1.0;
331 for (auto& child : node.list() | std::ranges::views::drop(1))
332 {
333 Node ev = evaluate(child, depth + 1, is_not_body);
334 if (ev.nodeType() != NodeType::Number)
335 return node;
336 v *= ev.number();
337 }
338 return Node(v);
339 }
340 else if (name == "/" && is_not_body)
341 {
342 checkMacroArgCountGe(node, 2, "/", "operator");
343 const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
344 if (one.nodeType() != NodeType::Number)
345 return node;
346
347 double v = one.number();
348 for (auto& child : node.list() | std::ranges::views::drop(2))
349 {
350 Node ev = evaluate(child, depth + 1, is_not_body);
351 if (ev.nodeType() != NodeType::Number)
352 return node;
353 v /= ev.number();
354 }
355 return Node(v);
356 }
357 else if (name == "not" && is_not_body)
358 {
359 checkMacroArgCountEq(node, 1, "not", "condition");
360 return (!isTruthy(evaluate(node.list()[1], depth + 1, is_not_body))) ? getTrueNode() : getFalseNode();
361 }
362 else if (name == Language::And && is_not_body)
363 {
364 if (node.list().size() < 3)
365 throwMacroProcessingError(fmt::format("Interpreting a `{}' chain with {} arguments, expected at least 2.", Language::And, argcount), node);
366
367 for (std::size_t i = 1, end = node.list().size(); i < end; ++i)
368 {
369 if (!isTruthy(evaluate(node.list()[i], depth + 1, is_not_body)))
370 return getFalseNode();
371 }
372 return getTrueNode();
373 }
374 else if (name == Language::Or && is_not_body)
375 {
376 if (node.list().size() < 3)
377 throwMacroProcessingError(fmt::format("Interpreting an `{}' chain with {} arguments, expected at least 2.", Language::Or, argcount), node);
378
379 for (std::size_t i = 1, end = node.list().size(); i < end; ++i)
380 {
381 if (isTruthy(evaluate(node.list()[i], depth + 1, is_not_body)))
382 return getTrueNode();
383 }
384 return getFalseNode();
385 }
386 else if (name == "len")
387 {
388 if (node.list().size() > 2)
389 throwMacroProcessingError(fmt::format("When expanding `len' inside a macro, got {} arguments, expected 1", argcount), node);
390 if (Node& lst = node.list()[1]; lst.nodeType() == NodeType::List) // only apply len at compile time if we can
391 {
392 if (isConstEval(lst))
393 {
394 if (!lst.list().empty() && lst.list()[0] == getListNode())
395 node.updateValueAndType(Node(static_cast<long>(lst.list().size()) - 1));
396 else
397 node.updateValueAndType(Node(static_cast<long>(lst.list().size())));
398 }
399 }
400 }
401 else if (name == "empty?")
402 {
403 if (node.list().size() > 2)
404 throwMacroProcessingError(fmt::format("When expanding `empty?' inside a macro, got {} arguments, expected 1", argcount), node);
405 if (Node& lst = node.list()[1]; lst.nodeType() == NodeType::List && isConstEval(lst))
406 {
407 // only apply len at compile time if we can
408 if (!lst.list().empty() && lst.list()[0] == getListNode())
409 node.updateValueAndType(lst.list().size() - 1 == 0 ? getTrueNode() : getFalseNode());
410 else
411 node.updateValueAndType(lst.list().empty() ? getTrueNode() : getFalseNode());
412 }
413 else if (lst == getNilNode())
415 }
416 else if (name == "@")
417 {
418 checkMacroArgCountEq(node, 2, "@");
419
420 Node sublist = evaluate(node.list()[1], depth + 1, is_not_body);
421 const Node idx = evaluate(node.list()[2], depth + 1, is_not_body);
422
423 if (sublist.nodeType() == NodeType::List && idx.nodeType() == NodeType::Number)
424 {
425 const std::size_t size = sublist.list().size();
426 std::size_t real_size = size;
427 long num_idx = static_cast<long>(idx.number());
428
429 // if the first node is the function call to "list", don't count it
430 if (size > 0 && sublist.list()[0] == getListNode())
431 {
432 real_size--;
433 if (num_idx >= 0)
434 ++num_idx;
435 }
436
437 Node output;
438 if (num_idx >= 0 && std::cmp_less(num_idx, size))
439 output = sublist.list()[static_cast<std::size_t>(num_idx)];
440 else if (const auto c = static_cast<long>(size) + num_idx; num_idx < 0 && std::cmp_less(c, size) && c >= 0)
441 output = sublist.list()[static_cast<std::size_t>(c)];
442 else
443 throwMacroProcessingError(fmt::format("Index ({}) out of range (list size: {})", num_idx, real_size), node);
444
445 output.setFilename(node.filename());
446 output.setPos(node.line(), node.col());
447 return output;
448 }
449 }
450 else if (name == "head")
451 {
452 if (node.list().size() > 2)
453 throwMacroProcessingError(fmt::format("When expanding `head' inside a macro, got {} arguments, expected 1", argcount), node);
454 if (node.list()[1].nodeType() == NodeType::List)
455 {
456 Node& sublist = node.list()[1];
457 if (!sublist.constList().empty() && sublist.constList()[0] == getListNode())
458 {
459 if (sublist.constList().size() > 1)
460 {
461 const Node sublistCopy = sublist.constList()[1];
462 node.updateValueAndType(sublistCopy);
463 }
464 else
466 }
467 else if (!sublist.list().empty())
468 node.updateValueAndType(sublist.constList()[0]);
469 else
471 }
472 }
473 else if (name == "tail")
474 {
475 if (node.list().size() > 2)
476 throwMacroProcessingError(fmt::format("When expanding `tail' inside a macro, got {} arguments, expected 1", argcount), node);
477 if (node.list()[1].nodeType() == NodeType::List)
478 {
479 Node sublist = node.list()[1];
480 if (!sublist.list().empty() && sublist.list()[0] == getListNode())
481 {
482 if (sublist.list().size() > 1)
483 {
484 sublist.list().erase(sublist.constList().begin() + 1);
485 node.updateValueAndType(sublist);
486 }
487 else
488 {
490 node.push_back(getListNode());
491 }
492 }
493 else if (!sublist.list().empty())
494 {
495 sublist.list().erase(sublist.constList().begin());
496 sublist.list().insert(sublist.list().begin(), getListNode());
497 node.updateValueAndType(sublist);
498 }
499 else
500 {
502 node.push_back(getListNode());
503 }
504 }
505 }
506 else if (name == Language::Symcat)
507 {
508 if (node.list().size() <= 2)
509 throwMacroProcessingError(fmt::format("When expanding `{}', expected at least 2 arguments, got {} arguments", Language::Symcat, argcount), node);
510 if (node.list()[1].nodeType() != NodeType::Symbol)
512 fmt::format(
513 "When expanding `{}', expected the first argument to be a Symbol, got a {}: {}",
515 typeToString(node.list()[1]),
516 node.list()[1].repr()),
517 node);
518
519 std::string sym = node.list()[1].string();
520
521 for (std::size_t i = 2, end = node.list().size(); i < end; ++i)
522 {
523 const Node ev = evaluate(node.list()[i], depth + 1, /* is_not_body */ true);
524
525 switch (ev.nodeType())
526 {
527 case NodeType::Number:
528 // we don't want '.' in identifiers
529 sym += std::to_string(static_cast<long int>(ev.number()));
530 break;
531
532 case NodeType::String:
533 case NodeType::Symbol:
534 sym += ev.string();
535 break;
536
537 default:
539 fmt::format(
540 "When expanding `{}', expected either a Number, String or Symbol, got a {}: {}",
542 typeToString(ev),
543 ev.repr()),
544 ev);
545 }
546 }
547
549 node.setString(sym);
550 }
551 else if (name == Language::Argcount)
552 {
553 const Node sym = node.constList()[1];
554 if (sym.nodeType() == NodeType::Symbol)
555 {
556 if (const auto maybe_func = lookupDefinedFunction(sym.string()); maybe_func.has_value())
557 node.updateValueAndType(Node(static_cast<long>(maybe_func->constList().size())));
558 else
559 throwMacroProcessingError(fmt::format("When expanding `{}', expected a known function name, got unbound variable {}", Language::Argcount, sym.string()), sym);
560 }
561 else if (sym.nodeType() == NodeType::List && sym.constList().size() == 3 && sym.constList()[0].nodeType() == NodeType::Keyword && sym.constList()[0].keyword() == Keyword::Fun)
562 node.updateValueAndType(Node(static_cast<long>(sym.constList()[1].constList().size())));
563 else
564 throwMacroProcessingError(fmt::format("When trying to apply `{}', got a {} instead of a Symbol or Function", Language::Argcount, typeToString(sym)), sym);
565 }
566 else if (name == Language::Repr)
567 {
568 const Node ast = node.constList()[1];
570 }
571 else if (name == Language::Paste)
572 {
573 if (node.list().size() != 2)
574 throwMacroProcessingError(fmt::format("When expanding `{}', expected one argument, got {} arguments", Language::Paste, argcount), node);
575 return node.constList()[1];
576 }
577 else if (name == Language::Undef)
578 {
579 if (node.list().size() != 2)
580 throwMacroProcessingError(fmt::format("When expanding `{}', expected one argument, got {} arguments", Language::Undef, argcount), node);
581
582 const Node sym = node.constList()[1];
583 if (sym.nodeType() == NodeType::Symbol)
584 {
587 return node;
588 }
589
591 fmt::format(
592 "When expanding `{}', got a {}. Can not un-define a macro without a valid name",
594 sym);
595 }
596 }
597
598 if (node.nodeType() == NodeType::List && !node.constList().empty())
599 {
600 for (auto& child : node.list())
601 child.updateValueAndType(evaluate(child, depth + 1, is_not_body));
602 }
603
604 if (node.nodeType() == NodeType::Spread)
605 throwMacroProcessingError(fmt::format("Found an unevaluated spread: `{}'", node.string()), node);
606
607 return node;
608 }
609
611 {
612 if (node.nodeType() == NodeType::Symbol)
613 {
614 if (node.string() == "true")
615 return true;
616 if (node.string() == "false" || node.string() == "nil")
617 return false;
618 }
619 else if ((node.nodeType() == NodeType::Number && node.number() != 0.0) || (node.nodeType() == NodeType::String && !node.string().empty()))
620 return true;
621 else if (node.nodeType() == NodeType::Spread)
622 throwMacroProcessingError("Can not determine the truth value of a spreaded symbol", node);
623 return false;
624 }
625
626 std::optional<Node> MacroProcessor::lookupDefinedFunction(const std::string& name) const
627 {
628 if (m_defined_functions.contains(name))
629 return m_defined_functions.at(name);
630 return std::nullopt;
631 }
632
633 const Node* MacroProcessor::findNearestMacro(const std::string& name) const
634 {
635 if (m_macros.empty())
636 return nullptr;
637
638 for (const auto& m_macro : std::ranges::reverse_view(m_macros))
639 {
640 if (const auto res = m_macro.has(name); res != nullptr)
641 return res;
642 }
643 return nullptr;
644 }
645
646 void MacroProcessor::deleteNearestMacro(const std::string& name)
647 {
648 if (m_macros.empty())
649 return;
650
651 for (auto& m_macro : std::ranges::reverse_view(m_macros))
652 {
653 if (m_macro.remove(name))
654 {
655 // stop right here because we found one matching macro
656 return;
657 }
658 }
659 }
660
662 {
663 return node.nodeType() == NodeType::List &&
664 !node.constList().empty() &&
665 node.constList()[0].nodeType() == NodeType::Keyword &&
666 node.constList()[0].keyword() == Keyword::Begin;
667 }
668
669 void MacroProcessor::removeBegin(Node& node, std::size_t i)
670 {
671 if (node.isListLike() && node.list()[i].nodeType() == NodeType::List && !node.list()[i].list().empty())
672 {
673 Node lst = node.constList()[i];
674 Node first = lst.constList()[0];
675
676 if (first.nodeType() == NodeType::Keyword && first.keyword() == Keyword::Begin)
677 {
678 const std::size_t previous = i;
679
680 for (std::size_t block_idx = 1, end = lst.constList().size(); block_idx < end; ++block_idx)
681 node.list().insert(
682 node.constList().begin() + static_cast<std::vector<Node>::difference_type>(i + block_idx),
683 lst.list()[block_idx]);
684
685 node.list().erase(node.constList().begin() + static_cast<std::vector<Node>::difference_type>(previous));
686 }
687 }
688 }
689
690 bool MacroProcessor::isConstEval(const Node& node) const
691 {
692 switch (node.nodeType())
693 {
694 case NodeType::Symbol:
695 {
696 const auto it = std::ranges::find(Language::operators, node.string());
697 const auto it2 = std::ranges::find_if(Builtins::builtins,
698 [&node](const std::pair<std::string, Value>& element) -> bool {
699 return node.string() == element.first;
700 });
701
702 return it != Language::operators.end() ||
703 it2 != Builtins::builtins.end() ||
704 findNearestMacro(node.string()) != nullptr ||
705 node.string() == "list" ||
706 node.string() == "nil";
707 }
708
709 case NodeType::List:
710 return std::ranges::all_of(node.constList(), [this](const Node& child) {
711 return isConstEval(child);
712 });
713
715 case NodeType::Field:
716 return false;
717
719 case NodeType::String:
720 case NodeType::Number:
721 case NodeType::Macro:
722 case NodeType::Spread:
724 case NodeType::Unused:
725 return true;
726 }
727
728 return false;
729 }
730
731 void MacroProcessor::throwMacroProcessingError(const std::string& message, const Node& node)
732 {
733 throw CodeError(message, node.filename(), node.line(), node.col(), node.repr());
734 }
735}
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.
bool shouldTrace() const
Definition Logger.hpp:39
void trace(const char *fmt, Args &&... args)
Write a trace level log using fmtlib.
Definition Logger.hpp:98
void debug(const char *fmt, Args &&... args)
Write a debug level log using fmtlib.
Definition Logger.hpp:65
void traceStart(std::string &&trace_name)
Definition Logger.hpp:75
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:60
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.
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
static bool isTruthy(const Node &node)
Check if a node can be evaluated to true.
static void throwMacroProcessingError(const std::string &message, const Node &node)
Throw a macro processing error.
std::unordered_map< std::string, Node > m_defined_functions
Definition Processor.hpp:62
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:61
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
static 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.
static 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.
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:31
NodeType nodeType() const noexcept
Return the node type.
Definition Node.cpp:77
void setNodeType(NodeType type) noexcept
Set the Node Type object.
Definition Node.cpp:98
bool isListLike() const noexcept
Check if the node is a list like node.
Definition Node.cpp:82
const std::string & filename() const noexcept
Return the filename in which this node was created.
Definition Node.cpp:145
const std::string & string() const noexcept
Return the string held by the value (if the node type allows it)
Definition Node.cpp:37
void setPos(std::size_t line, std::size_t col) noexcept
Set the Position of the node in the text.
Definition Node.cpp:108
const std::vector< Node > & constList() const noexcept
Return the list of sub-nodes held by the node.
Definition Node.cpp:72
Keyword keyword() const noexcept
Return the keyword held by the value (if the node type allows it)
Definition Node.cpp:47
Namespace & arkNamespace() noexcept
Return the namespace held by the value (if the node type allows it)
Definition Node.cpp:52
std::string repr() const noexcept
Compute a representation of the node without any comments or additional sugar, colors,...
Definition Node.cpp:160
void setFilename(const std::string &filename) noexcept
Set the original Filename where the node was.
Definition Node.cpp:114
std::ostream & debugPrint(std::ostream &os) const noexcept
Print a node to an output stream with added type annotations.
Definition Node.cpp:233
std::size_t col() const noexcept
Get the column at which this node was created.
Definition Node.cpp:140
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:62
void setString(const std::string &value) noexcept
Set the String object.
Definition Node.cpp:103
double number() const noexcept
Return the number held by the value (if the node type allows it)
Definition Node.cpp:42
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:92
std::size_t line() const noexcept
Get the line at which this node was created.
Definition Node.cpp:135
std::vector< Node > & list() noexcept
Return the list of sub-nodes held by the node.
Definition Node.cpp:67
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 Argcount
Definition Common.hpp:120
constexpr std::array< std::string_view, 24 > operators
Definition Common.hpp:135
constexpr std::string_view Paste
Definition Common.hpp:122
constexpr std::string_view And
Definition Common.hpp:115
constexpr std::string_view Repr
Definition Common.hpp:121
constexpr std::string_view Or
Definition Common.hpp:116
constexpr std::string_view Symcat
Definition Common.hpp:119
constexpr std::string_view Undef
Definition Common.hpp:118
std::string typeToString(const Node &node) noexcept
Definition Node.hpp:241
const Node & getNilNode()
Definition Node.cpp:320
Keyword
The different keywords available.
Definition Common.hpp:60
const Node & getFalseNode()
Definition Node.cpp:314
const Node & getListNode()
Definition Node.cpp:326
const Node & getTrueNode()
Definition Node.cpp:308
constexpr std::size_t MaxMacroProcessingDepth
Controls the number of recursive calls to MacroProcessor::processNode.
Definition Constants.hpp:66
CodeError thrown by the compiler (parser, macro processor, optimizer, and compiler itself)
std::shared_ptr< Node > ast
Definition Namespace.hpp:18