ArkScript
A small, fast, functional and scripting language for video games
Processor.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4#include <ranges>
5#include <sstream>
6#include <fmt/core.h>
7
8#include <Ark/Exceptions.hpp>
13
14namespace Ark::internal
15{
16 MacroProcessor::MacroProcessor(const unsigned debug) noexcept :
17 m_debug(debug)
18 {
19 // create executors pipeline
20 m_executor_pipeline = MacroExecutorPipeline(
21 { std::make_shared<SymbolExecutor>(this),
22 std::make_shared<ConditionalExecutor>(this),
23 std::make_shared<FunctionExecutor>(this) });
24
25 m_predefined_macros = {
26 "symcat",
27 "argcount",
28 "$repr" // TODO: unify predefined macro names (update documentation and examples and tests)
29 };
30 }
31
33 {
34 if (m_debug >= 2)
35 std::cout << "Processing macros...\n";
36
37 // to be able to modify it
38 m_ast = ast;
40
41 if (m_debug >= 3)
42 {
43 std::cout << "(MacroProcessor) AST after processing macros\n";
44 m_ast.debugPrint(std::cout) << '\n';
45 }
46 }
47
48 const Node& MacroProcessor::ast() const noexcept
49 {
50 return m_ast;
51 }
52
54 {
55 // a macro needs at least 2 nodes, name + value is the minimal form
56 if (node.constList().size() < 2)
57 throwMacroProcessingError("Invalid macro, missing value", node);
58
59 const Node& first_node = node.list()[0];
60 const Node& second_node = node.list()[1];
61
62 // ($ name value)
63 if (node.constList().size() == 2)
64 {
65 if (first_node.nodeType() == NodeType::Symbol)
66 {
67 if (first_node.string() != "$undef")
68 m_macros.back().add(first_node.string(), node);
69 else if (second_node.nodeType() == NodeType::Symbol) // un-define a macro
70 deleteNearestMacro(second_node.string());
71 else // used undef on a non-symbol
72 throwMacroProcessingError("Can not un-define a macro without a name", second_node);
73 }
74 else
75 throwMacroProcessingError("Can not define a macro without a symbol", first_node);
76 }
77 // ($ name (args) body)
78 else if (node.constList().size() == 3 && first_node.nodeType() == NodeType::Symbol)
79 {
80 if (second_node.nodeType() != NodeType::List)
81 throwMacroProcessingError("Invalid macro argument's list", second_node);
82 bool had_spread = false;
83 for (const Node& n : second_node.constList())
84 {
85 if (n.nodeType() != NodeType::Symbol && n.nodeType() != NodeType::Spread)
86 throwMacroProcessingError("Invalid macro argument's list, expected symbols", n);
87 if (n.nodeType() == NodeType::Spread)
88 {
89 if (had_spread)
90 throwMacroProcessingError("Invalid macro, multiple spread detected in argument list but only one is allowed", n);
91 had_spread = true;
92 }
93 else if (had_spread && n.nodeType() == NodeType::Symbol)
94 throwMacroProcessingError(fmt::format("Invalid macro, a spread should mark the end of an argument list, but found another argument: {}", n.string()), n);
95 }
96 m_macros.back().add(first_node.string(), node);
97 }
98 }
99
101 {
102 if (node.nodeType() == NodeType::List && !node.constList().empty() && node.constList()[0].nodeType() == NodeType::Keyword)
103 {
104 Keyword kw = node.constList()[0].keyword();
105 // checking for function definition, which can occur only inside an assignment node
106 if (kw != Keyword::Let && kw != Keyword::Mut && kw != Keyword::Set)
107 return;
108
109 const Node& inner = node.constList()[2];
110 if (inner.nodeType() != NodeType::List)
111 return;
112
113 if (!inner.constList().empty() && inner.constList()[0].nodeType() == NodeType::Keyword && inner.constList()[0].keyword() == Keyword::Fun)
114 m_defined_functions[node.constList()[1].string()] = inner.constList()[1];
115 }
116 }
117
118 void MacroProcessor::processNode(Node& node, unsigned depth)
119 {
120 if (node.nodeType() == NodeType::List)
121 {
122 bool has_created = false;
123 // recursive call
124 std::size_t i = 0;
125 while (i < node.list().size())
126 {
127 if (node.list()[i].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) || !has_created)
131 {
132 has_created = true;
133 m_macros.emplace_back(depth);
134 }
135
136 const bool had = hadBegin(node.list()[i]);
137
138 registerMacro(node.list()[i]);
139 if (node.list()[i].nodeType() == NodeType::Macro)
140 recurApply(node.list()[i]);
141
142 if (hadBegin(node.list()[i]) && !had)
143 removeBegin(node, i);
144 else if (node.list()[i].nodeType() == NodeType::Macro || node.list()[i].nodeType() == NodeType::Unused)
145 node.list().erase(node.constList().begin() + i);
146 }
147 else // running on non-macros
148 {
149 bool added_begin = false;
150
151 const bool had = hadBegin(node.list()[i]);
152 applyMacro(node.list()[i]);
153 recurApply(node.list()[i]);
154
155 if (hadBegin(node.list()[i]) && !had)
156 added_begin = true;
157 else if (node.list()[i].nodeType() == NodeType::Unused)
158 node.list().erase(node.constList().begin() + i);
159
160 if (node.nodeType() == NodeType::List)
161 {
162 processNode(node.list()[i], depth + 1);
163 // needed if we created a function node from a macro
164 registerFuncDef(node.list()[i]);
165 }
166
167 // remove begins in macros
168 if (added_begin)
169 removeBegin(node, i);
170
171 // go forward only if it isn't a macro, because we delete macros
172 // while running on the AST
173 ++i;
174 }
175 }
176
177 // delete a scope only if needed
178 if (!m_macros.empty() && m_macros.back().depth() == depth)
179 m_macros.pop_back();
180 }
181 }
182
184 {
185 return m_executor_pipeline.applyMacro(node);
186 }
187
188 void MacroProcessor::unify(const std::unordered_map<std::string, Node>& map, Node& target, Node* parent, const std::size_t index)
189 {
190 if (target.nodeType() == NodeType::Symbol)
191 {
192 if (const auto p = map.find(target.string()); p != map.end())
193 target = p->second;
194 }
195 else if (target.isListLike())
196 {
197 for (std::size_t i = 0, end = target.list().size(); i < end; ++i)
198 unify(map, target.list()[i], &target, i);
199 }
200 else if (target.nodeType() == NodeType::Spread)
201 {
202 Node sub_node = target;
204 unify(map, sub_node, parent);
205
206 if (sub_node.nodeType() != NodeType::List)
207 throwMacroProcessingError(fmt::format("Can not unify a {} to a Spread", typeToString(sub_node)), sub_node);
208
209 for (std::size_t i = 1, end = sub_node.list().size(); i < end; ++i)
210 parent->list().insert(parent->list().begin() + index + i, sub_node.list()[i]);
211 parent->list().erase(parent->list().begin() + index); // remove the spread
212 }
213 }
214
215 Node MacroProcessor::evaluate(Node& node, const bool is_not_body)
216 {
217 if (node.nodeType() == NodeType::Symbol)
218 {
219 const Node* macro = findNearestMacro(node.string());
220 if (macro != nullptr && macro->constList().size() == 2)
221 return macro->constList()[1];
222 return node;
223 }
224 if (node.nodeType() == NodeType::List && node.constList().size() > 1 && node.list()[0].nodeType() == NodeType::Symbol)
225 {
226#define GEN_NOT_BODY(str_name, error_handler, ret) \
227 else if (name == (str_name) && is_not_body) \
228 { \
229 if (node.list().size() != 3) (error_handler); \
230 Node one = evaluate(node.list()[1], is_not_body), \
231 two = evaluate(node.list()[2], is_not_body); \
232 return ret; \
233 }
234
235#define GEN_COMPARATOR(str_name, cond) GEN_NOT_BODY( \
236 str_name, \
237 throwMacroProcessingError( \
238 fmt::format("Interpreting a `{}' condition with {} arguments, expected 2.", str_name, argcount), \
239 node), \
240 (cond) ? getTrueNode() : getFalseNode())
241
242#define GEN_OP(str_name, op) GEN_NOT_BODY( \
243 str_name, \
244 throwMacroProcessingError( \
245 fmt::format("Interpreting a `{}' operation with {} arguments, expected 2.", str_name, argcount), \
246 node), \
247 (one.nodeType() == two.nodeType() && two.nodeType() == NodeType::Number) ? Node(one.number() op two.number()) : node)
248
249 const std::string& name = node.list()[0].string();
250 const std::size_t argcount = node.list().size() - 1;
251 if (const Node* macro = findNearestMacro(name); macro != nullptr)
252 {
253 applyMacro(node.list()[0]);
254 if (node.list()[0].nodeType() == NodeType::Unused)
255 node.list().erase(node.constList().begin());
256 }
257 GEN_COMPARATOR("=", one == two)
258 GEN_COMPARATOR("!=", !(one == two))
259 GEN_COMPARATOR("<", one < two)
260 GEN_COMPARATOR(">", !(one < two) && !(one == two))
261 GEN_COMPARATOR("<=", one < two || one == two)
262 GEN_COMPARATOR(">=", !(one < two))
263 GEN_OP("+", +)
264 GEN_OP("-", -)
265 GEN_OP("*", *)
266 GEN_OP("/", /)
267 else if (name == "not" && is_not_body)
268 {
269 if (node.list().size() != 2)
270 throwMacroProcessingError(fmt::format("Interpreting a `not' condition with {} arguments, expected 1.", argcount), node);
271
272 return (!isTruthy(evaluate(node.list()[1], is_not_body))) ? getTrueNode() : getFalseNode();
273 }
274 else if (name == "and" && is_not_body)
275 {
276 if (node.list().size() < 3)
277 throwMacroProcessingError(fmt::format("Interpreting a `and' chain with {} arguments, expected at least 2.", argcount), node);
278
279 for (std::size_t i = 1, end = node.list().size(); i < end; ++i)
280 {
281 if (!isTruthy(evaluate(node.list()[i], is_not_body)))
282 return getFalseNode();
283 }
284 return getTrueNode();
285 }
286 else if (name == "or" && is_not_body)
287 {
288 if (node.list().size() < 3)
289 throwMacroProcessingError(fmt::format("Interpreting an `or' chain with {} arguments, expected at least 2.", argcount), node);
290
291 for (std::size_t i = 1, end = node.list().size(); i < end; ++i)
292 {
293 if (isTruthy(evaluate(node.list()[i], is_not_body)))
294 return getTrueNode();
295 }
296 return getFalseNode();
297 }
298 else if (name == "len")
299 {
300 if (node.list().size() > 2)
301 throwMacroProcessingError(fmt::format("When expanding `len' inside a macro, got {} arguments, expected 1", argcount), node);
302 if (Node& lst = node.list()[1]; lst.nodeType() == NodeType::List) // only apply len at compile time if we can
303 {
304 if (isConstEval(lst))
305 {
306 if (!lst.list().empty() && lst.list()[0] == getListNode())
307 node = Node(static_cast<long>(lst.list().size()) - 1);
308 else
309 node = Node(static_cast<long>(lst.list().size()));
310 }
311 }
312 }
313 else if (name == "@")
314 {
315 if (node.list().size() != 3)
316 throwMacroProcessingError(fmt::format("Interpreting a `@' with {} arguments, expected 2.", argcount), node);
317
318 Node sublist = evaluate(node.list()[1], is_not_body);
319 const Node idx = evaluate(node.list()[2], is_not_body);
320
321 if (sublist.nodeType() == NodeType::List && idx.nodeType() == NodeType::Number)
322 {
323 const long size = static_cast<long>(sublist.list().size());
324 long real_size = size;
325 long num_idx = static_cast<long>(idx.number());
326
327 // if the first node is the function call to "list", don't count it
328 if (size > 0 && sublist.list()[0] == getListNode())
329 {
330 real_size--;
331 if (num_idx >= 0)
332 ++num_idx;
333 }
334 num_idx = num_idx >= 0 ? num_idx : size + num_idx;
335
336 if (num_idx < size)
337 return sublist.list()[num_idx];
338 throwMacroProcessingError(fmt::format("Index ({}) out of range (list size: {})", num_idx, real_size), node);
339 }
340 }
341 else if (name == "head")
342 {
343 if (node.list().size() > 2)
344 throwMacroProcessingError(fmt::format("When expanding `head' inside a macro, got {} arguments, expected 1", argcount), node);
345 if (node.list()[1].nodeType() == NodeType::List)
346 {
347 Node& sublist = node.list()[1];
348 if (!sublist.constList().empty() && sublist.constList()[0] == getListNode())
349 {
350 if (sublist.constList().size() > 1)
351 {
352 const Node sublistCopy = sublist.constList()[1];
353 node = sublistCopy;
354 }
355 else
356 node = getNilNode();
357 }
358 else if (!sublist.list().empty())
359 node = sublist.constList()[0];
360 else
361 node = getNilNode();
362 }
363 }
364 else if (name == "tail")
365 {
366 if (node.list().size() > 2)
367 throwMacroProcessingError(fmt::format("When expanding `tail' inside a macro, got {} arguments, expected 1", argcount), node);
368 if (node.list()[1].nodeType() == NodeType::List)
369 {
370 Node sublist = node.list()[1];
371 if (!sublist.list().empty() && sublist.list()[0] == getListNode())
372 {
373 if (sublist.list().size() > 1)
374 {
375 sublist.list().erase(sublist.constList().begin() + 1);
376 node = sublist;
377 }
378 else
379 {
380 node = Node(NodeType::List);
381 node.push_back(getListNode());
382 }
383 }
384 else if (!sublist.list().empty())
385 {
386 sublist.list().erase(sublist.constList().begin());
387 node = sublist;
388 }
389 else
390 {
391 node = Node(NodeType::List);
392 node.push_back(getListNode());
393 }
394 }
395 }
396 else if (name == "symcat")
397 {
398 if (node.list().size() <= 2)
399 throwMacroProcessingError(fmt::format("When expanding `symcat', expected at least 2 arguments, got {} arguments", argcount), node);
400 if (node.list()[1].nodeType() != NodeType::Symbol)
401 throwMacroProcessingError(fmt::format("When expanding `symcat', expected the first argument to be a Symbol, got a {}", typeToString(node.list()[1])), node);
402
403 std::string sym = node.list()[1].string();
404
405 for (std::size_t i = 2, end = node.list().size(); i < end; ++i)
406 {
407 Node ev = evaluate(node.list()[i], /* is_not_body */ true);
408
409 switch (ev.nodeType())
410 {
411 case NodeType::Number:
412 sym += std::to_string(static_cast<long int>(ev.number())); // we don't want '.' in identifiers
413 break;
414
415 case NodeType::String:
416 case NodeType::Symbol:
417 sym += ev.string();
418 break;
419
420 default:
421 throwMacroProcessingError(fmt::format("When expanding `symcat', expected either a Number, String or Symbol, got a {}", typeToString(ev)), ev);
422 }
423 }
424
426 node.setString(sym);
427 }
428 else if (name == "argcount")
429 {
430 Node sym = node.constList()[1];
431 if (sym.nodeType() == NodeType::Symbol)
432 {
433 if (const auto it = m_defined_functions.find(sym.string()); it != m_defined_functions.end())
434 node = Node(static_cast<long>(it->second.constList().size()));
435 else
436 throwMacroProcessingError(fmt::format("When expanding `argcount', expected a known function name, got unbound variable {}", sym.string()), sym);
437 }
438 else if (sym.nodeType() == NodeType::List && sym.list().size() == 3 && sym.list()[0].nodeType() == NodeType::Keyword && sym.list()[0].keyword() == Keyword::Fun)
439 node = Node(static_cast<long>(sym.list()[1].list().size()));
440 else
441 throwMacroProcessingError(fmt::format("When trying to apply `argcount', got a {} instead of a Symbol or Function", typeToString(sym)), sym);
442 }
443 else if (name == "$repr")
444 {
445 const Node ast = node.constList()[1];
446 node = Node(NodeType::String, ast.repr());
447 }
448 }
449
450 if (node.nodeType() == NodeType::List && !node.constList().empty())
451 {
452 for (auto& child : node.list())
453 child = evaluate(child, is_not_body);
454 }
455
456 return node;
457 }
458
460 {
461 if (node.nodeType() == NodeType::Symbol)
462 {
463 if (node.string() == "true")
464 return true;
465 if (node.string() == "false" || node.string() == "nil")
466 return false;
467 }
468 else if ((node.nodeType() == NodeType::Number && node.number() != 0.0) || (node.nodeType() == NodeType::String && !node.string().empty()))
469 return true;
470 else if (node.nodeType() == NodeType::Spread)
471 throwMacroProcessingError("Can not determine the truth value of a spreaded symbol", node);
472 return false;
473 }
474
475 const Node* MacroProcessor::findNearestMacro(const std::string& name) const
476 {
477 if (m_macros.empty())
478 return nullptr;
479
480 for (const auto& m_macro : std::ranges::reverse_view(m_macros))
481 {
482 if (const auto res = m_macro.has(name); res != nullptr)
483 return res;
484 }
485 return nullptr;
486 }
487
488 void MacroProcessor::deleteNearestMacro(const std::string& name)
489 {
490 if (m_macros.empty())
491 return;
492
493 for (auto& m_macro : std::ranges::reverse_view(m_macros))
494 {
495 if (m_macro.remove(name))
496 {
497 // stop right here because we found one matching macro
498 return;
499 }
500 }
501 }
502
503 bool MacroProcessor::isPredefined(const std::string& symbol)
504 {
505 const auto it = std::ranges::find(m_predefined_macros, symbol);
506 return it != m_predefined_macros.end();
507 }
508
510 {
511 if (applyMacro(node) && node.isListLike())
512 {
513 for (auto& child : node.list())
514 recurApply(child);
515 }
516 }
517
519 {
520 return node.nodeType() == NodeType::List &&
521 !node.constList().empty() &&
522 node.constList()[0].nodeType() == NodeType::Keyword &&
523 node.constList()[0].keyword() == Keyword::Begin;
524 }
525
526 void MacroProcessor::removeBegin(Node& node, std::size_t i)
527 {
528 if (node.isListLike() && node.list()[i].nodeType() == NodeType::List && !node.list()[i].list().empty())
529 {
530 Node lst = node.constList()[i];
531 Node first = lst.constList()[0];
532
533 if (first.nodeType() == NodeType::Keyword && first.keyword() == Keyword::Begin)
534 {
535 const std::size_t previous = i;
536
537 for (std::size_t block_idx = 1, end = lst.constList().size(); block_idx < end; ++block_idx)
538 node.list().insert(node.constList().begin() + i + block_idx, lst.list()[block_idx]);
539
540 node.list().erase(node.constList().begin() + previous);
541 }
542 }
543 }
544
545 bool MacroProcessor::isConstEval(const Node& node) const
546 {
547 switch (node.nodeType())
548 {
549 case NodeType::Symbol:
550 {
551 const auto it = std::ranges::find(operators, node.string());
552 const auto it2 = std::ranges::find_if(Builtins::builtins,
553 [&node](const std::pair<std::string, Value>& element) -> bool {
554 return node.string() == element.first;
555 });
556
557 return it != operators.end() ||
558 it2 != Builtins::builtins.end() ||
559 findNearestMacro(node.string()) != nullptr ||
560 node.string() == "list";
561 }
562
563 case NodeType::List:
564 return std::ranges::all_of(node.constList(), [this](const Node& child) {
565 return isConstEval(child);
566 });
567
569 case NodeType::Field:
570 return false;
571
573 case NodeType::String:
574 case NodeType::Number:
575 case NodeType::Macro:
576 case NodeType::Spread:
577 case NodeType::Unused:
578 return true;
579 }
580
581 return false;
582 }
583
584 void MacroProcessor::throwMacroProcessingError(const std::string& message, const Node& node)
585 {
586 throw CodeError(message, node.filename(), node.line(), node.col(), node.repr());
587 }
588}
Host the declaration of all the ArkScript builtins.
Executor for Conditional Macros.
ArkScript homemade exceptions.
Executor for List Macros.
#define GEN_OP(str_name, op)
#define GEN_COMPARATOR(str_name, cond)
Handles the macros and their expansion in ArkScript source code.
Executor for Symbol Macros.
The class that initializes the MacroExecutors.
Definition: Pipeline.hpp:28
bool applyMacro(Node &node) const
Passes node through all MacroExecutors sequentially.
Definition: Pipeline.cpp:9
void registerFuncDef(const Node &node)
Registers a function definition node.
Definition: Processor.cpp:100
void unify(const std::unordered_map< std::string, Node > &map, Node &target, Node *parent, std::size_t index=0)
Unify a target node with a given map symbol => replacement node, recursively.
Definition: Processor.cpp:188
const Node & ast() const noexcept
Return the modified AST.
Definition: Processor.cpp:48
MacroExecutorPipeline m_executor_pipeline
Definition: Processor.hpp:58
void recurApply(Node &node)
Recursively apply macros on a given node.
Definition: Processor.cpp:509
std::vector< std::string > m_predefined_macros
Already existing macros, non-keywords, non-builtins.
Definition: Processor.hpp:59
void registerMacro(Node &node)
Registers macros based on their type.
Definition: Processor.cpp:53
static void removeBegin(Node &node, std::size_t i)
Remove a begin block added by a macro.
Definition: Processor.cpp:526
Node evaluate(Node &node, bool is_not_body=false)
Evaluate only the macros.
Definition: Processor.cpp:215
const Node * findNearestMacro(const std::string &name) const
Find the nearest macro matching a given name.
Definition: Processor.cpp:475
Node m_ast
The modified AST.
Definition: Processor.hpp:56
static bool isTruthy(const Node &node)
Check if a node can be evaluated to true.
Definition: Processor.cpp:459
static bool hadBegin(const Node &node)
Check if a given node is a list node, and starts with a Begin.
Definition: Processor.cpp:518
static void throwMacroProcessingError(const std::string &message, const Node &node)
Throw a macro processing error.
Definition: Processor.cpp:584
std::unordered_map< std::string, Node > m_defined_functions
Definition: Processor.hpp:60
bool isConstEval(const Node &node) const
Check if a node can be evaluated at compile time.
Definition: Processor.cpp:545
bool isPredefined(const std::string &symbol)
Check if a given symbol is a predefined macro or not.
Definition: Processor.cpp:503
MacroProcessor(unsigned debug) noexcept
Construct a new Macro Processor object.
Definition: Processor.cpp:16
void process(const Node &ast)
Send the complete AST and work on it.
Definition: Processor.cpp:32
void processNode(Node &node, unsigned depth)
Register macros in scopes and apply them as needed.
Definition: Processor.cpp:118
std::vector< MacroScope > m_macros
Handling macros in a scope fashion.
Definition: Processor.hpp:57
bool applyMacro(Node &node) const
Apply a macro on a given node.
Definition: Processor.cpp:183
unsigned m_debug
The debug level.
Definition: Processor.hpp:55
void deleteNearestMacro(const std::string &name)
Find the nearest macro matching a given name and delete it.
Definition: Processor.cpp:488
A node of an Abstract Syntax Tree for ArkScript.
Definition: Node.hpp:30
NodeType nodeType() const noexcept
Return the node type.
Definition: Node.cpp:71
void setNodeType(NodeType type) noexcept
Set the Node Type object.
Definition: Node.cpp:81
bool isListLike() const noexcept
Check if the node is a list like node.
Definition: Node.cpp:76
const std::string & filename() const noexcept
Return the filename in which this node was created.
Definition: Node.cpp:128
const std::string & string() const noexcept
Return the string held by the value (if the node type allows it)
Definition: Node.cpp:41
const std::vector< Node > & constList() const noexcept
Return the list of sub-nodes held by the node.
Definition: Node.cpp:66
Keyword keyword() const noexcept
Return the keyword held by the value (if the node type allows it)
Definition: Node.cpp:51
std::string repr() const noexcept
Compute a representation of the node without any comments or additional sugar, colors,...
Definition: Node.cpp:143
std::ostream & debugPrint(std::ostream &os) const noexcept
Print a node to an output stream with added type annotations.
Definition: Node.cpp:209
std::size_t col() const noexcept
Get the column at which this node was created.
Definition: Node.cpp:123
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:56
void setString(const std::string &value) noexcept
Set the String object.
Definition: Node.cpp:86
double number() const noexcept
Return the number held by the value (if the node type allows it)
Definition: Node.cpp:46
std::size_t line() const noexcept
Get the line at which this node was created.
Definition: Node.cpp:118
std::vector< Node > & list() noexcept
Return the list of sub-nodes held by the node.
Definition: Node.cpp:61
const std::vector< std::pair< std::string, Value > > builtins
std::string typeToString(const Node &node) noexcept
Definition: Node.hpp:200
constexpr std::array< std::string_view, 25 > operators
Definition: Common.hpp:84
const Node & getNilNode()
Definition: Node.cpp:293
Keyword
The different keywords available.
Definition: Common.hpp:56
const Node & getFalseNode()
Definition: Node.cpp:287
const Node & getListNode()
Definition: Node.cpp:299
const Node & getTrueNode()
Definition: Node.cpp:281
CodeError thrown by the compiler (parser, macro processor, optimizer, and compiler itself)
Definition: Exceptions.hpp:85