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