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 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 std::string& kind)
212 {
213 const std::size_t argcount = node.constList().size();
214 if (argcount != expected + 1)
216 fmt::format(
217 "Interpreting a `{}'{} with {} arguments, expected {}.",
218 name,
219 kind.empty() ? kind : " " + kind,
220 argcount,
221 expected),
222 node);
223 }
224
225 void MacroProcessor::checkMacroArgCountGe(const Node& node, std::size_t expected, const std::string& name, const std::string& kind)
226 {
227 const std::size_t argcount = node.constList().size();
228 if (argcount < expected + 1)
230 fmt::format(
231 "Interpreting a `{}'{} with {} arguments, expected at least {}.",
232 name,
233 kind.empty() ? kind : " " + kind,
234 argcount,
235 expected),
236 node);
237 }
238
239 Node MacroProcessor::evaluate(Node& node, const unsigned depth, const bool is_not_body)
240 {
241 if (node.nodeType() == NodeType::Symbol)
242 {
243 const Node* macro = findNearestMacro(node.string());
244 if (macro != nullptr && macro->constList().size() == 2)
245 return macro->constList()[1];
246 return node;
247 }
248 if (node.nodeType() == NodeType::List && !node.constList().empty() && node.list()[0].nodeType() == NodeType::Symbol)
249 {
250 const std::string& name = node.list()[0].string();
251 const std::size_t argcount = node.list().size() - 1;
252
253 if (const Node* macro = findNearestMacro(name); macro != nullptr)
254 {
255 applyMacro(node.list()[0], depth + 1);
256 if (node.list()[0].nodeType() == NodeType::Unused)
257 node.list().erase(node.constList().begin());
258 }
259 else if (name == "=" && is_not_body)
260 {
261 checkMacroArgCountEq(node, 2, "=", "condition");
262 const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
263 const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
264 return (one == two) ? getTrueNode() : getFalseNode();
265 }
266 else if (name == "!=" && is_not_body)
267 {
268 checkMacroArgCountEq(node, 2, "!=", "condition");
269 const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
270 const Node two = evaluate(node.list()[2], depth + 1, is_not_body);
271 return (one != two) ? getTrueNode() : getFalseNode();
272 }
273 else if (name == "<" && is_not_body)
274 {
275 checkMacroArgCountEq(node, 2, "<", "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, ">", "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) && (one != two) ? getTrueNode() : getFalseNode();
286 }
287 else if (name == "<=" && is_not_body)
288 {
289 checkMacroArgCountEq(node, 2, "<=", "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 || one == two ? getTrueNode() : getFalseNode();
293 }
294 else if (name == ">=" && is_not_body)
295 {
296 checkMacroArgCountEq(node, 2, ">=", "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) ? getTrueNode() : getFalseNode();
300 }
301 else if (name == "+" && is_not_body)
302 {
303 checkMacroArgCountGe(node, 2, "+", "operator");
304 double v = 0.0;
305 for (auto& child : node.list() | std::ranges::views::drop(1))
306 {
307 Node ev = evaluate(child, depth + 1, is_not_body);
308 if (ev.nodeType() != NodeType::Number)
309 return node;
310 v += ev.number();
311 }
312 return Node(v);
313 }
314 else if (name == "-" && is_not_body)
315 {
316 checkMacroArgCountGe(node, 2, "-", "operator");
317 const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
318 if (one.nodeType() != NodeType::Number)
319 return node;
320
321 double v = one.number();
322 for (auto& child : node.list() | std::ranges::views::drop(2))
323 {
324 Node ev = evaluate(child, depth + 1, is_not_body);
325 if (ev.nodeType() != NodeType::Number)
326 return node;
327 v -= ev.number();
328 }
329 return Node(v);
330 }
331 else if (name == "*" && is_not_body)
332 {
333 checkMacroArgCountGe(node, 2, "*", "operator");
334 double v = 1.0;
335 for (auto& child : node.list() | std::ranges::views::drop(1))
336 {
337 Node ev = evaluate(child, depth + 1, is_not_body);
338 if (ev.nodeType() != NodeType::Number)
339 return node;
340 v *= ev.number();
341 }
342 return Node(v);
343 }
344 else if (name == "/" && is_not_body)
345 {
346 checkMacroArgCountGe(node, 2, "/", "operator");
347 const Node one = evaluate(node.list()[1], depth + 1, is_not_body);
348 if (one.nodeType() != NodeType::Number)
349 return node;
350
351 double v = one.number();
352 for (auto& child : node.list() | std::ranges::views::drop(2))
353 {
354 Node ev = evaluate(child, depth + 1, is_not_body);
355 if (ev.nodeType() != NodeType::Number)
356 return node;
357 v /= ev.number();
358 }
359 return Node(v);
360 }
361 else if (name == "not" && is_not_body)
362 {
363 checkMacroArgCountEq(node, 1, "not", "condition");
364 return (!isTruthy(evaluate(node.list()[1], depth + 1, is_not_body))) ? getTrueNode() : getFalseNode();
365 }
366 else if (name == Language::And && is_not_body)
367 {
368 if (node.list().size() < 3)
369 throwMacroProcessingError(fmt::format("Interpreting a `{}' chain with {} arguments, expected at least 2.", Language::And, argcount), node);
370
371 for (std::size_t i = 1, end = node.list().size(); i < end; ++i)
372 {
373 if (!isTruthy(evaluate(node.list()[i], depth + 1, is_not_body)))
374 return getFalseNode();
375 }
376 return getTrueNode();
377 }
378 else if (name == Language::Or && is_not_body)
379 {
380 if (node.list().size() < 3)
381 throwMacroProcessingError(fmt::format("Interpreting an `{}' chain with {} arguments, expected at least 2.", Language::Or, argcount), node);
382
383 for (std::size_t i = 1, end = node.list().size(); i < end; ++i)
384 {
385 if (isTruthy(evaluate(node.list()[i], depth + 1, is_not_body)))
386 return getTrueNode();
387 }
388 return getFalseNode();
389 }
390 else if (name == "len")
391 {
392 if (node.list().size() > 2)
393 throwMacroProcessingError(fmt::format("When expanding `len' inside a macro, got {} arguments, expected 1", argcount), node);
394 if (Node& lst = node.list()[1]; lst.nodeType() == NodeType::List) // only apply len at compile time if we can
395 {
396 if (isConstEval(lst))
397 {
398 if (!lst.list().empty() && lst.list()[0] == getListNode())
399 node.updateValueAndType(Node(static_cast<long>(lst.list().size()) - 1));
400 else
401 node.updateValueAndType(Node(static_cast<long>(lst.list().size())));
402 }
403 }
404 }
405 else if (name == "empty?")
406 {
407 if (node.list().size() > 2)
408 throwMacroProcessingError(fmt::format("When expanding `empty?' inside a macro, got {} arguments, expected 1", argcount), node);
409 if (Node& lst = node.list()[1]; lst.nodeType() == NodeType::List && isConstEval(lst))
410 {
411 // only apply len at compile time if we can
412 if (!lst.list().empty() && lst.list()[0] == getListNode())
413 node.updateValueAndType(lst.list().size() - 1 == 0 ? getTrueNode() : getFalseNode());
414 else
415 node.updateValueAndType(lst.list().empty() ? getTrueNode() : getFalseNode());
416 }
417 else if (lst == getNilNode())
419 }
420 else if (name == "@")
421 {
422 checkMacroArgCountEq(node, 2, "@");
423
424 Node sublist = evaluate(node.list()[1], depth + 1, is_not_body);
425 const Node idx = evaluate(node.list()[2], depth + 1, is_not_body);
426
427 if (sublist.nodeType() == NodeType::List && idx.nodeType() == NodeType::Number)
428 {
429 const std::size_t size = sublist.list().size();
430 std::size_t real_size = size;
431 long num_idx = static_cast<long>(idx.number());
432
433 // if the first node is the function call to "list", don't count it
434 if (size > 0 && sublist.list()[0] == getListNode())
435 {
436 real_size--;
437 if (num_idx >= 0)
438 ++num_idx;
439 }
440
441 Node output;
442 if (num_idx >= 0 && std::cmp_less(num_idx, size))
443 output = sublist.list()[static_cast<std::size_t>(num_idx)];
444 else if (const auto c = static_cast<long>(size) + num_idx; num_idx < 0 && std::cmp_less(c, size) && c >= 0)
445 output = sublist.list()[static_cast<std::size_t>(c)];
446 else
447 throwMacroProcessingError(fmt::format("Index ({}) out of range (list size: {})", num_idx, real_size), node);
448
449 output.setFilename(node.filename());
450 output.setPos(node.line(), node.col());
451 return output;
452 }
453 }
454 else if (name == "head")
455 {
456 if (node.list().size() > 2)
457 throwMacroProcessingError(fmt::format("When expanding `head' inside a macro, got {} arguments, expected 1", argcount), node);
458 if (node.list()[1].nodeType() == NodeType::List)
459 {
460 Node& sublist = node.list()[1];
461 if (!sublist.constList().empty() && sublist.constList()[0] == getListNode())
462 {
463 if (sublist.constList().size() > 1)
464 {
465 const Node sublistCopy = sublist.constList()[1];
466 node.updateValueAndType(sublistCopy);
467 }
468 else
470 }
471 else if (!sublist.list().empty())
472 node.updateValueAndType(sublist.constList()[0]);
473 else
475 }
476 }
477 else if (name == "tail")
478 {
479 if (node.list().size() > 2)
480 throwMacroProcessingError(fmt::format("When expanding `tail' inside a macro, got {} arguments, expected 1", argcount), node);
481 if (node.list()[1].nodeType() == NodeType::List)
482 {
483 Node sublist = node.list()[1];
484 if (!sublist.list().empty() && sublist.list()[0] == getListNode())
485 {
486 if (sublist.list().size() > 1)
487 {
488 sublist.list().erase(sublist.constList().begin() + 1);
489 node.updateValueAndType(sublist);
490 }
491 else
492 {
494 node.push_back(getListNode());
495 }
496 }
497 else if (!sublist.list().empty())
498 {
499 sublist.list().erase(sublist.constList().begin());
500 sublist.list().insert(sublist.list().begin(), getListNode());
501 node.updateValueAndType(sublist);
502 }
503 else
504 {
506 node.push_back(getListNode());
507 }
508 }
509 }
510 else if (name == Language::Symcat)
511 {
512 if (node.list().size() <= 2)
513 throwMacroProcessingError(fmt::format("When expanding `{}', expected at least 2 arguments, got {} arguments", Language::Symcat, argcount), node);
514 if (node.list()[1].nodeType() != NodeType::Symbol)
516 fmt::format(
517 "When expanding `{}', expected the first argument to be a Symbol, got a {}: {}",
519 typeToString(node.list()[1]),
520 node.list()[1].repr()),
521 node);
522
523 std::string sym = node.list()[1].string();
524
525 for (std::size_t i = 2, end = node.list().size(); i < end; ++i)
526 {
527 const Node ev = evaluate(node.list()[i], depth + 1, /* is_not_body */ true);
528
529 switch (ev.nodeType())
530 {
531 case NodeType::Number:
532 // we don't want '.' in identifiers
533 sym += std::to_string(static_cast<long int>(ev.number()));
534 break;
535
536 case NodeType::String:
537 case NodeType::Symbol:
538 sym += ev.string();
539 break;
540
541 default:
543 fmt::format(
544 "When expanding `{}', expected either a Number, String or Symbol, got a {}: {}",
546 typeToString(ev),
547 ev.repr()),
548 ev);
549 }
550 }
551
553 node.setString(sym);
554 }
555 else if (name == Language::Argcount)
556 {
557 const Node sym = node.constList()[1];
558 if (sym.nodeType() == NodeType::Symbol)
559 {
560 if (const auto maybe_func = lookupDefinedFunction(sym.string()); maybe_func.has_value())
561 node.updateValueAndType(Node(static_cast<long>(maybe_func->constList().size())));
562 else
563 throwMacroProcessingError(fmt::format("When expanding `{}', expected a known function name, got unbound variable {}", Language::Argcount, sym.string()), sym);
564 }
565 else if (sym.nodeType() == NodeType::List && sym.constList().size() == 3 && sym.constList()[0].nodeType() == NodeType::Keyword && sym.constList()[0].keyword() == Keyword::Fun)
566 node.updateValueAndType(Node(static_cast<long>(sym.constList()[1].constList().size())));
567 else
568 throwMacroProcessingError(fmt::format("When trying to apply `{}', got a {} instead of a Symbol or Function", Language::Argcount, typeToString(sym)), sym);
569 }
570 else if (name == Language::Repr)
571 {
572 const Node arg = node.constList()[1];
574 }
575 else if (name == Language::AsIs)
576 {
577 if (node.list().size() != 2)
578 throwMacroProcessingError(fmt::format("When expanding `{}', expected one argument, got {} arguments", Language::AsIs, argcount), node);
579 return node.constList()[1];
580 }
581 else if (name == Language::Undef)
582 {
583 if (node.list().size() != 2)
584 throwMacroProcessingError(fmt::format("When expanding `{}', expected one argument, got {} arguments", Language::Undef, argcount), node);
585
586 const Node sym = node.constList()[1];
587 if (sym.nodeType() == NodeType::Symbol)
588 {
591 return node;
592 }
593
595 fmt::format(
596 "When expanding `{}', got a {}. Can not un-define a macro without a valid name",
598 sym);
599 }
600 else if (name == "$type")
601 {
602 const Node arg = node.constList()[1];
604 }
605 }
606
607 if (node.nodeType() == NodeType::List && !node.constList().empty())
608 {
609 for (auto& child : node.list())
610 child.updateValueAndType(evaluate(child, depth + 1, is_not_body));
611 }
612
613 if (node.nodeType() == NodeType::Spread)
614 throwMacroProcessingError(fmt::format("Found an unevaluated spread: `{}'", node.string()), node);
615
616 return node;
617 }
618
620 {
621 if (node.nodeType() == NodeType::Symbol)
622 {
623 if (node.string() == "true")
624 return true;
625 if (node.string() == "false" || node.string() == "nil")
626 return false;
627 }
628 else if ((node.nodeType() == NodeType::Number && node.number() != 0.0) || (node.nodeType() == NodeType::String && !node.string().empty()))
629 return true;
630 else if (node.nodeType() == NodeType::Spread)
631 throwMacroProcessingError("Can not determine the truth value of a spread symbol", node);
632 return false;
633 }
634
635 std::optional<Node> MacroProcessor::lookupDefinedFunction(const std::string& name) const
636 {
637 if (m_defined_functions.contains(name))
638 return m_defined_functions.at(name);
639 return std::nullopt;
640 }
641
642 const Node* MacroProcessor::findNearestMacro(const std::string& name) const
643 {
644 if (m_macros.empty())
645 return nullptr;
646
647 for (const auto& m_macro : std::ranges::reverse_view(m_macros))
648 {
649 if (const auto res = m_macro.has(name); res != nullptr)
650 return res;
651 }
652 return nullptr;
653 }
654
655 void MacroProcessor::deleteNearestMacro(const std::string& name)
656 {
657 if (m_macros.empty())
658 return;
659
660 for (auto& m_macro : std::ranges::reverse_view(m_macros))
661 {
662 if (m_macro.remove(name))
663 {
664 // stop right here because we found one matching macro
665 return;
666 }
667 }
668 }
669
671 {
672 return node.nodeType() == NodeType::List &&
673 !node.constList().empty() &&
674 node.constList()[0].nodeType() == NodeType::Keyword &&
675 node.constList()[0].keyword() == Keyword::Begin;
676 }
677
678 void MacroProcessor::removeBegin(Node& node, const std::size_t i)
679 {
680 if (node.isListLike() && node.list()[i].nodeType() == NodeType::List && !node.list()[i].list().empty())
681 {
682 Node lst = node.constList()[i];
683 Node first = lst.constList()[0];
684
685 if (first.nodeType() == NodeType::Keyword && first.keyword() == Keyword::Begin)
686 {
687 const std::size_t previous = i;
688
689 for (std::size_t block_idx = 1, end = lst.constList().size(); block_idx < end; ++block_idx)
690 node.list().insert(
691 node.constList().begin() + static_cast<std::vector<Node>::difference_type>(i + block_idx),
692 lst.list()[block_idx]);
693
694 node.list().erase(node.constList().begin() + static_cast<std::vector<Node>::difference_type>(previous));
695 }
696 }
697 }
698
699 bool MacroProcessor::isConstEval(const Node& node) const
700 {
701 switch (node.nodeType())
702 {
703 case NodeType::Symbol:
704 {
705 const auto it = std::ranges::find(Language::operators, node.string());
706 const auto it2 = std::ranges::find_if(Builtins::builtins,
707 [&node](const std::pair<std::string, Value>& element) -> bool {
708 return node.string() == element.first;
709 });
710
711 return it != Language::operators.end() ||
712 it2 != Builtins::builtins.end() ||
713 findNearestMacro(node.string()) != nullptr ||
714 node.string() == "list" ||
715 node.string() == "nil";
716 }
717
718 case NodeType::List:
719 return std::ranges::all_of(node.constList(), [this](const Node& child) {
720 return isConstEval(child);
721 });
722
724 case NodeType::Field:
725 return false;
726
728 case NodeType::String:
729 case NodeType::Number:
730 case NodeType::Macro:
731 case NodeType::Spread:
733 case NodeType::Unused:
734 return true;
735 }
736
737 return false;
738 }
739
740 void MacroProcessor::throwMacroProcessingError(const std::string& message, const Node& node) const
741 {
742 const std::optional<CodeErrorContext> maybe_context = [this]() -> std::optional<CodeErrorContext> {
743 if (!m_macros_being_applied.empty())
744 {
745 const Node& origin = m_macros_being_applied.front();
746 return CodeErrorContext(
747 origin.filename(),
748 origin.line(),
749 origin.col(),
750 origin.repr(),
751 /* from_macro_expansion= */ true);
752 }
753 return std::nullopt;
754 }();
755
756 throw CodeError(
757 message,
759 node.filename(),
760 node.line(),
761 node.col(),
762 node.repr()),
763 maybe_context);
764 }
765}
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:38
void trace(const char *fmt, Args &&... args)
Write a trace level log using fmtlib.
Definition Logger.hpp:97
void debug(const char *fmt, Args &&... args)
Write a debug level log using fmtlib.
Definition Logger.hpp:64
void traceStart(std::string &&trace_name)
Definition Logger.hpp:74
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 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:30
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 AsIs
Definition Common.hpp:137
constexpr std::string_view Argcount
Definition Common.hpp:135
constexpr std::array< std::string_view, 24 > operators
Definition Common.hpp:150
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:240
const Node & getNilNode()
Definition Node.cpp:320
Keyword
The different keywords available.
Definition Common.hpp:75
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