ArkScript
A small, fast, functional and scripting language for video games
ASTLowerer.cpp
Go to the documentation of this file.
2
3#include <limits>
4#include <utility>
5#include <algorithm>
6#include <fmt/core.h>
7#include <fmt/color.h>
8
9#include <Ark/Literals.hpp>
11
12namespace Ark::internal
13{
14 using namespace literals;
15
16 ASTLowerer::ASTLowerer(const unsigned debug) :
17 m_logger("ASTLowerer", debug)
18 {}
19
20 void ASTLowerer::process(const Node& ast)
21 {
22 m_logger.traceStart("process");
23 m_code_pages.emplace_back(); // create empty page
24
25 // gather symbols, values, and start to create code segments
27 ast,
28 /* current_page */ Page { .index = 0, .is_temp = false },
29 /* is_result_unused */ false,
30 /* is_terminal */ false);
32 }
33
34 const std::vector<IR::Block>& ASTLowerer::intermediateRepresentation() const noexcept
35 {
36 return m_code_pages;
37 }
38
39 const std::vector<std::string>& ASTLowerer::symbols() const noexcept
40 {
41 return m_symbols;
42 }
43
44 const std::vector<ValTableElem>& ASTLowerer::values() const noexcept
45 {
46 return m_values;
47 }
48
49 std::optional<Instruction> ASTLowerer::getOperator(const std::string& name) noexcept
50 {
51 const auto it = std::ranges::find(Language::operators, name);
52 if (it != Language::operators.end())
53 return static_cast<Instruction>(std::distance(Language::operators.begin(), it) + FIRST_OPERATOR);
54 return std::nullopt;
55 }
56
57 std::optional<uint16_t> ASTLowerer::getBuiltin(const std::string& name) noexcept
58 {
59 const auto it = std::ranges::find_if(Builtins::builtins,
60 [&name](const std::pair<std::string, Value>& element) -> bool {
61 return name == element.first;
62 });
63 if (it != Builtins::builtins.end())
64 return static_cast<uint16_t>(std::distance(Builtins::builtins.begin(), it));
65 return std::nullopt;
66 }
67
68 std::optional<Instruction> ASTLowerer::getListInstruction(const std::string& name) noexcept
69 {
70 const auto it = std::ranges::find(Language::listInstructions, name);
71 if (it != Language::listInstructions.end())
72 return static_cast<Instruction>(std::distance(Language::listInstructions.begin(), it) + LIST);
73 return std::nullopt;
74 }
75
77 {
78 if (node.nodeType() == NodeType::List && !node.constList().empty() && node.constList()[0].nodeType() == NodeType::Keyword)
79 return (node.constList()[0].keyword() == Keyword::Begin && node.constList().size() > 1) ||
80 node.constList()[0].keyword() == Keyword::Fun ||
81 node.constList()[0].keyword() == Keyword::If;
82 return true; // any other node, function call, symbol, number...
83 }
84
85 bool ASTLowerer::isUnaryInst(const Instruction inst) noexcept
86 {
87 switch (inst)
88 {
89 case NOT: [[fallthrough]];
90 case LEN: [[fallthrough]];
91 case EMPTY: [[fallthrough]];
92 case TAIL: [[fallthrough]];
93 case HEAD: [[fallthrough]];
94 case ISNIL: [[fallthrough]];
95 case TO_NUM: [[fallthrough]];
96 case TO_STR: [[fallthrough]];
97 case TYPE:
98 return true;
99
100 default:
101 return false;
102 }
103 }
104
105 bool ASTLowerer::isTernaryInst(const Instruction inst) noexcept
106 {
107 switch (inst)
108 {
109 case AT_AT:
110 return true;
111
112 default:
113 return false;
114 }
115 }
116
117 void ASTLowerer::warning(const std::string& message, const Node& node)
118 {
119 fmt::println("{} {}", fmt::styled("Warning", fmt::fg(fmt::color::dark_orange)), Diagnostics::makeContextWithNode(message, node));
120 }
121
122 void ASTLowerer::buildAndThrowError(const std::string& message, const Node& node)
123 {
124 throw CodeError(message, CodeErrorContext(node.filename(), node.line(), node.col(), node.repr()));
125 }
126
127 void ASTLowerer::compileExpression(const Node& x, const Page p, const bool is_result_unused, const bool is_terminal, const std::string& var_name)
128 {
129 // register symbols
130 if (x.nodeType() == NodeType::Symbol)
131 compileSymbol(x, p, is_result_unused);
132 else if (x.nodeType() == NodeType::Field)
133 {
134 // the parser guarantees us that there is at least 2 elements (eg: a.b)
135 compileSymbol(x.constList()[0], p, is_result_unused);
136 for (auto it = x.constList().begin() + 1, end = x.constList().end(); it != end; ++it)
137 {
138 uint16_t i = addSymbol(*it);
139 page(p).emplace_back(GET_FIELD, i);
140 }
141 page(p).back().setSourceLocation(x.filename(), x.line());
142 }
143 // register values
144 else if (x.nodeType() == NodeType::String || x.nodeType() == NodeType::Number)
145 {
146 uint16_t i = addValue(x);
147
148 if (!is_result_unused)
149 page(p).emplace_back(LOAD_CONST, i);
150 }
151 // namespace nodes
152 else if (x.nodeType() == NodeType::Namespace)
153 compileExpression(*x.constArkNamespace().ast, p, is_result_unused, is_terminal, var_name);
154 else if (x.nodeType() == NodeType::Unused)
155 {
156 // do nothing, explicitly
157 }
158 // empty code block should be nil
159 else if (x.constList().empty())
160 {
161 if (!is_result_unused)
162 {
163 static const std::optional<uint16_t> nil = getBuiltin("nil");
164 page(p).emplace_back(BUILTIN, nil.value());
165 }
166 }
167 // list instructions
168 else if (const auto c0 = x.constList()[0]; c0.nodeType() == NodeType::Symbol && getListInstruction(c0.string()).has_value())
169 compileListInstruction(c0, x, p, is_result_unused);
170 // registering structures
171 else if (x.constList()[0].nodeType() == NodeType::Keyword)
172 {
173 switch (const Keyword keyword = x.constList()[0].keyword())
174 {
175 case Keyword::If:
176 compileIf(x, p, is_result_unused, is_terminal, var_name);
177 break;
178
179 case Keyword::Set:
180 [[fallthrough]];
181 case Keyword::Let:
182 [[fallthrough]];
183 case Keyword::Mut:
184 compileLetMutSet(keyword, x, p);
185 break;
186
187 case Keyword::Fun:
188 compileFunction(x, p, is_result_unused, var_name);
189 break;
190
191 case Keyword::Begin:
192 {
193 for (std::size_t i = 1, size = x.constList().size(); i < size; ++i)
195 x.constList()[i],
196 p,
197 // All the nodes in a begin (except for the last one) are producing a result that we want to drop.
198 (i != size - 1) || is_result_unused,
199 // If the begin is a terminal node, only its last node is terminal.
200 is_terminal && (i == size - 1),
201 var_name);
202 break;
203 }
204
205 case Keyword::While:
206 compileWhile(x, p);
207 break;
208
209 case Keyword::Import:
211 break;
212
213 case Keyword::Del:
214 page(p).emplace_back(DEL, addSymbol(x.constList()[1]));
215 page(p).back().setSourceLocation(x.constList()[1].filename(), x.constList()[1].line());
216 break;
217 }
218 }
219 else if (x.nodeType() == NodeType::List)
220 {
221 // if we are here, we should have a function name
222 // push arguments first, then function name, then call it
223 handleCalls(x, p, is_result_unused, is_terminal, var_name);
224 }
225 else
227 fmt::format(
228 "NodeType `{}' not handled in ASTLowerer::compileExpression. Please fill an issue on GitHub: https://github.com/ArkScript-lang/Ark",
229 typeToString(x)),
230 x);
231 }
232
233 void ASTLowerer::compileSymbol(const Node& x, const Page p, const bool is_result_unused)
234 {
235 const std::string& name = x.string();
236
237 if (const auto it_builtin = getBuiltin(name))
238 page(p).emplace_back(Instruction::BUILTIN, it_builtin.value());
239 else if (getOperator(name).has_value())
240 buildAndThrowError(fmt::format("Found a free standing operator: `{}`", name), x);
241 else
242 {
243 const std::optional<std::size_t> maybe_local_idx = m_locals_locator.lookupLastScopeByName(name);
244 if (maybe_local_idx.has_value())
245 page(p).emplace_back(LOAD_SYMBOL_BY_INDEX, static_cast<uint16_t>(maybe_local_idx.value()));
246 else
247 page(p).emplace_back(LOAD_SYMBOL, addSymbol(x));
248 }
249
250 if (is_result_unused)
251 {
252 warning("Statement has no effect", x);
253 page(p).emplace_back(POP);
254 }
255 }
256
257 void ASTLowerer::compileListInstruction(const Node& c0, const Node& x, const Page p, const bool is_result_unused)
258 {
259 std::string name = c0.string();
260 Instruction inst = getListInstruction(name).value();
261
262 // length of at least 1 since we got a symbol name
263 const auto argc = x.constList().size() - 1u;
264 // error, can not use append/concat/pop (and their in place versions) with a <2 length argument list
265 if (argc < 2 && APPEND <= inst && inst <= POP)
266 buildAndThrowError(fmt::format("Can not use {} with less than 2 arguments", name), c0);
267 if (inst <= POP && std::cmp_greater(argc, std::numeric_limits<uint16_t>::max()))
268 buildAndThrowError(fmt::format("Too many arguments ({}), exceeds 65'535", argc), x);
269 if (argc != 3 && inst == SET_AT_INDEX)
270 buildAndThrowError(fmt::format("Expected 3 arguments (list, index, value) for {}, got {}", name, argc), c0);
271 if (argc != 4 && inst == SET_AT_2_INDEX)
272 buildAndThrowError(fmt::format("Expected 4 arguments (list, y, x, value) for {}, got {}", name, argc), c0);
273
274 // compile arguments in reverse order
275 for (std::size_t i = x.constList().size() - 1u; i > 0; --i)
276 {
277 const auto node = x.constList()[i];
278 if (nodeProducesOutput(node))
279 compileExpression(node, p, false, false);
280 else
281 buildAndThrowError(fmt::format("Invalid node inside call to {}", name), node);
282 }
283
284 // put inst and number of arguments
285 std::size_t inst_argc = 0;
286 switch (inst)
287 {
288 case LIST:
289 inst_argc = argc;
290 break;
291
292 case APPEND:
293 case APPEND_IN_PLACE:
294 case CONCAT:
295 case CONCAT_IN_PLACE:
296 inst_argc = argc - 1;
297 break;
298
299 case POP_LIST:
301 inst_argc = 0;
302 break;
303
304 default:
305 break;
306 }
307 page(p).emplace_back(inst, static_cast<uint16_t>(inst_argc));
308 page(p).back().setSourceLocation(c0.filename(), c0.line());
309
310 if (is_result_unused && name.back() != '!' && inst <= POP_LIST_IN_PLACE) // in-place functions never push a value
311 {
312 warning("Ignoring return value of function", x);
313 page(p).emplace_back(POP);
314 }
315 }
316
317 void ASTLowerer::compileIf(const Node& x, const Page p, const bool is_result_unused, const bool is_terminal, const std::string& var_name)
318 {
319 // compile condition
320 compileExpression(x.constList()[1], p, false, false);
321 page(p).back().setSourceLocation(x.constList()[1].filename(), x.constList()[1].line());
322
323 // jump only if needed to the "true" branch
324 const auto label_then = IR::Entity::Label(m_current_label++);
325 page(p).emplace_back(IR::Entity::GotoIf(label_then, true));
326
327 // "false" branch code
328 if (x.constList().size() == 4) // we have an else clause
329 {
331 compileExpression(x.constList()[3], p, is_result_unused, is_terminal, var_name);
332 page(p).back().setSourceLocation(x.constList()[3].filename(), x.constList()[3].line());
334 }
335
336 // when else is finished, jump to end
337 const auto label_end = IR::Entity::Label(m_current_label++);
338 page(p).emplace_back(IR::Entity::Goto(label_end));
339
340 // absolute address to jump to if condition is true
341 page(p).emplace_back(label_then);
342 // if code
344 compileExpression(x.constList()[2], p, is_result_unused, is_terminal, var_name);
345 page(p).back().setSourceLocation(x.constList()[2].filename(), x.constList()[2].line());
347 // set jump to end pos
348 page(p).emplace_back(label_end);
349 }
350
351 void ASTLowerer::compileFunction(const Node& x, const Page p, const bool is_result_unused, const std::string& var_name)
352 {
353 if (const auto args = x.constList()[1]; args.nodeType() != NodeType::List)
354 buildAndThrowError(fmt::format("Expected a well formed argument(s) list, got a {}", typeToString(args)), args);
355 if (x.constList().size() != 3)
356 buildAndThrowError("Invalid node ; if it was computed by a macro, check that a node is returned", x);
357
358 // capture, if needed
359 std::size_t capture_inst_count = 0;
360 for (const auto& node : x.constList()[1].constList())
361 {
362 if (node.nodeType() == NodeType::Capture)
363 {
364 page(p).emplace_back(CAPTURE, addSymbol(node));
365 ++capture_inst_count;
366 }
367 }
368 const bool is_closure = capture_inst_count > 0;
369
371 is_closure
374
375 // create new page for function body
376 m_code_pages.emplace_back();
377 const auto function_body_page = Page { .index = m_code_pages.size() - 1, .is_temp = false };
378 // save page_id into the constants table as PageAddr and load the const
379 page(p).emplace_back(is_closure ? MAKE_CLOSURE : LOAD_CONST, addValue(function_body_page.index, x));
380
381 // pushing arguments from the stack into variables in the new scope
382 for (const auto& node : x.constList()[1].constList())
383 {
384 if (node.nodeType() == NodeType::Symbol)
385 {
386 page(function_body_page).emplace_back(STORE, addSymbol(node));
387 m_locals_locator.addLocal(node.string());
388 }
389 }
390
391 // push body of the function
392 compileExpression(x.constList()[2], function_body_page, false, true, var_name);
393
394 // return last value on the stack
395 page(function_body_page).emplace_back(RET);
397
398 // if the computed function is unused, pop it
399 if (is_result_unused)
400 {
401 warning("Unused declared function", x);
402 page(p).emplace_back(POP);
403 }
404 }
405
406 void ASTLowerer::compileLetMutSet(const Keyword n, const Node& x, const Page p)
407 {
408 if (const auto sym = x.constList()[1]; sym.nodeType() != NodeType::Symbol)
409 buildAndThrowError(fmt::format("Expected a symbol, got a {}", typeToString(sym)), sym);
410 if (x.constList().size() != 3)
411 buildAndThrowError("Invalid node ; if it was computed by a macro, check that a node is returned", x);
412
413 const std::string name = x.constList()[1].string();
414 uint16_t i = addSymbol(x.constList()[1]);
415
416 // put value before symbol id
417 // starting at index = 2 because x is a (let|mut|set variable ...) node
418 for (std::size_t idx = 2, end = x.constList().size(); idx < end; ++idx)
419 compileExpression(x.constList()[idx], p, false, false, name);
420
421 if (n == Keyword::Let || n == Keyword::Mut)
422 {
423 page(p).emplace_back(STORE, i);
425 }
426 else
427 page(p).emplace_back(SET_VAL, i);
428
429 page(p).back().setSourceLocation(x.filename(), x.line());
430 }
431
432 void ASTLowerer::compileWhile(const Node& x, const Page p)
433 {
434 if (x.constList().size() != 3)
435 buildAndThrowError("Invalid node ; if it was computed by a macro, check that a node is returned", x);
436
438 page(p).emplace_back(CREATE_SCOPE);
439 page(p).back().setSourceLocation(x.filename(), x.line());
440
441 // save current position to jump there at the end of the loop
442 const auto label_loop = IR::Entity::Label(m_current_label++);
443 page(p).emplace_back(label_loop);
444 // push condition
445 compileExpression(x.constList()[1], p, false, false);
446 // absolute jump to end of block if condition is false
447 const auto label_end = IR::Entity::Label(m_current_label++);
448 page(p).emplace_back(IR::Entity::GotoIf(label_end, false));
449 // push code to page
450 compileExpression(x.constList()[2], p, true, false);
451
452 // reset the scope at the end of the loop so that indices are still valid
453 // otherwise, (while true { (let a 5) (print a) (let b 6) (print b) })
454 // would print 5, 6, then only 6 as we emit LOAD_SYMBOL_FROM_INDEX 0 and b is the last in the scope
455 page(p).emplace_back(RESET_SCOPE);
456 // loop, jump to the condition
457 page(p).emplace_back(IR::Entity::Goto(label_loop));
458
459 // absolute address to jump to if condition is false
460 page(p).emplace_back(label_end);
461
462 page(p).emplace_back(POP_SCOPE);
464 }
465
467 {
468 std::string path;
469 const Node package_node = x.constList()[1];
470 for (std::size_t i = 0, end = package_node.constList().size(); i < end; ++i)
471 {
472 path += package_node.constList()[i].string();
473 if (i + 1 != end)
474 path += "/";
475 }
476 path += ".arkm";
477
478 // register plugin path in the constants table
479 uint16_t id = addValue(Node(NodeType::String, path));
480 // add plugin instruction + id of the constant referring to the plugin path
481 page(p).emplace_back(PLUGIN, id);
482 page(p).back().setSourceLocation(x.filename(), x.line());
483 }
484
485 void ASTLowerer::handleCalls(const Node& x, const Page p, bool is_result_unused, const bool is_terminal, const std::string& var_name)
486 {
487 constexpr std::size_t start_index = 1;
488
489 const auto node = x.constList()[0];
490 const std::optional<Instruction> maybe_operator = node.nodeType() == NodeType::Symbol ? getOperator(node.string()) : std::nullopt;
491
492 enum class ShortcircuitOp
493 {
494 And,
495 Or
496 };
497 const std::optional<ShortcircuitOp> maybe_shortcircuit =
498 node.nodeType() == NodeType::Symbol
499 ? (node.string() == Language::And
500 ? std::make_optional(ShortcircuitOp::And)
501 : (node.string() == Language::Or
502 ? std::make_optional(ShortcircuitOp::Or)
503 : std::nullopt))
504 : std::nullopt;
505
506 if (maybe_shortcircuit.has_value())
507 {
508 // short circuit implementation
509 if (x.constList().size() < 3)
511 fmt::format(
512 "Expected at least 2 arguments while compiling '{}', got {}",
513 node.string(),
514 x.constList().size() - 1),
515 x);
516
517 compileExpression(x.constList()[1], p, false, false);
518 page(p).emplace_back(DUP);
519
520 const auto label_shortcircuit = IR::Entity::Label(m_current_label++);
521 for (std::size_t i = 2, end = x.constList().size(); i < end; ++i)
522 {
523 switch (maybe_shortcircuit.value())
524 {
525 case ShortcircuitOp::And:
526 page(p).emplace_back(IR::Entity::GotoIf(label_shortcircuit, false));
527 break;
528 case ShortcircuitOp::Or:
529 page(p).emplace_back(IR::Entity::GotoIf(label_shortcircuit, true));
530 break;
531 }
532 page(p).emplace_back(POP);
533
534 compileExpression(x.constList()[i], p, false, false);
535 if (i + 1 != end)
536 page(p).emplace_back(DUP);
537 }
538
539 page(p).emplace_back(label_shortcircuit);
540 }
541 else if (!maybe_operator.has_value())
542 {
543 if (is_terminal && x.constList()[0].nodeType() == NodeType::Symbol && var_name == x.constList()[0].string())
544 {
545 // push the arguments in reverse order
546 for (std::size_t i = x.constList().size() - 1; i >= start_index; --i)
547 {
548 if (nodeProducesOutput(x.constList()[i]))
549 compileExpression(x.constList()[i], p, false, false);
550 else
551 buildAndThrowError(fmt::format("Invalid node inside tail call to `{}'", node.repr()), x);
552 }
553
554 // jump to the top of the function
555 page(p).emplace_back(JUMP, 0_u16);
556 page(p).back().setSourceLocation(node.filename(), node.line());
557 return; // skip the potential Instruction::POP at the end
558 }
559 else
560 {
561 m_temp_pages.emplace_back();
562 const auto proc_page = Page { .index = m_temp_pages.size() - 1u, .is_temp = true };
563 // closure chains have been handled (eg: closure.field.field.function)
564 compileExpression(node, proc_page, false, false); // storing proc
565 if (m_temp_pages.back().empty())
566 buildAndThrowError(fmt::format("Can not call {}", x.constList()[0].repr()), x);
567
568 // push arguments on current page
569 for (auto exp = x.constList().begin() + start_index, exp_end = x.constList().end(); exp != exp_end; ++exp)
570 {
571 if (nodeProducesOutput(*exp))
572 compileExpression(*exp, p, false, false);
573 else
574 buildAndThrowError(fmt::format("Invalid node inside call to `{}'", node.repr()), x);
575 }
576 // push proc from temp page
577 for (const auto& inst : m_temp_pages.back())
578 page(p).push_back(inst);
579 m_temp_pages.pop_back();
580
581 // number of arguments
582 std::size_t args_count = 0;
583 for (auto it = x.constList().begin() + 1, it_end = x.constList().end(); it != it_end; ++it)
584 {
585 if (it->nodeType() != NodeType::Capture)
586 args_count++;
587 }
588 // call the procedure
589 page(p).emplace_back(CALL, args_count);
590 page(p).back().setSourceLocation(node.filename(), node.line());
591 }
592 }
593 else // operator
594 {
595 // retrieve operator
596 auto op = maybe_operator.value();
597
598 if (op == ASSERT)
599 is_result_unused = false;
600
601 // push arguments on current page
602 std::size_t exp_count = 0;
603 for (std::size_t index = start_index, size = x.constList().size(); index < size; ++index)
604 {
605 if (nodeProducesOutput(x.constList()[index]))
606 compileExpression(x.constList()[index], p, false, false);
607 else
608 buildAndThrowError(fmt::format("Invalid node inside call to operator `{}'", node.repr()), x);
609
610 if ((index + 1 < size && x.constList()[index + 1].nodeType() != NodeType::Capture) || index + 1 == size)
611 exp_count++;
612
613 // in order to be able to handle things like (op A B C D...)
614 // which should be transformed into A B op C op D op...
615 if (exp_count >= 2 && !isTernaryInst(op))
616 page(p).emplace_back(op);
617 }
618
619 if (isUnaryInst(op))
620 {
621 if (exp_count != 1)
622 buildAndThrowError(fmt::format("Operator needs one argument, but was called with {}", exp_count), x.constList()[0]);
623 page(p).emplace_back(op);
624 }
625 else if (isTernaryInst(op))
626 {
627 if (exp_count != 3)
628 buildAndThrowError(fmt::format("Operator needs three arguments, but was called with {}", exp_count), x.constList()[0]);
629 page(p).emplace_back(op);
630 }
631 else if (exp_count <= 1)
632 buildAndThrowError(fmt::format("Operator needs two arguments, but was called with {}", exp_count), x.constList()[0]);
633
634 page(p).back().setSourceLocation(x.filename(), x.line());
635
636 // need to check we didn't push the (op A B C D...) things for operators not supporting it
637 if (exp_count > 2)
638 {
639 switch (op)
640 {
641 // authorized instructions
642 case ADD: [[fallthrough]];
643 case SUB: [[fallthrough]];
644 case MUL: [[fallthrough]];
645 case DIV: [[fallthrough]];
646 case MOD: [[fallthrough]];
647 case AT_AT:
648 break;
649
650 default:
652 fmt::format(
653 "`{}' requires 2 arguments, but got {}.",
654 Language::operators[static_cast<std::size_t>(op - FIRST_OPERATOR)],
655 exp_count),
656 x);
657 }
658 }
659 }
660
661 if (is_result_unused)
662 page(p).emplace_back(POP);
663 }
664
665 uint16_t ASTLowerer::addSymbol(const Node& sym)
666 {
667 // otherwise, add the symbol, and return its id in the table
668 auto it = std::ranges::find(m_symbols, sym.string());
669 if (it == m_symbols.end())
670 {
671 m_symbols.push_back(sym.string());
672 it = m_symbols.begin() + static_cast<std::vector<std::string>::difference_type>(m_symbols.size() - 1);
673 }
674
675 const auto distance = std::distance(m_symbols.begin(), it);
676 if (distance < std::numeric_limits<uint16_t>::max())
677 return static_cast<uint16_t>(distance);
678 buildAndThrowError("Too many symbols (exceeds 65'536), aborting compilation.", sym);
679 }
680
681 uint16_t ASTLowerer::addValue(const Node& x)
682 {
683 const ValTableElem v(x);
684 auto it = std::ranges::find(m_values, v);
685 if (it == m_values.end())
686 {
687 m_values.push_back(v);
688 it = m_values.begin() + static_cast<std::vector<ValTableElem>::difference_type>(m_values.size() - 1);
689 }
690
691 const auto distance = std::distance(m_values.begin(), it);
692 if (distance < std::numeric_limits<uint16_t>::max())
693 return static_cast<uint16_t>(distance);
694 buildAndThrowError("Too many values (exceeds 65'536), aborting compilation.", x);
695 }
696
697 uint16_t ASTLowerer::addValue(const std::size_t page_id, const Node& current)
698 {
699 const ValTableElem v(page_id);
700 auto it = std::ranges::find(m_values, v);
701 if (it == m_values.end())
702 {
703 m_values.push_back(v);
704 it = m_values.begin() + static_cast<std::vector<ValTableElem>::difference_type>(m_values.size() - 1);
705 }
706
707 const auto distance = std::distance(m_values.begin(), it);
708 if (distance < std::numeric_limits<uint16_t>::max())
709 return static_cast<uint16_t>(distance);
710 buildAndThrowError("Too many values (exceeds 65'536), aborting compilation.", current);
711 }
712}
Host the declaration of all the ArkScript builtins.
User defined literals for Ark internals.
uint16_t addValue(const Node &x)
Register a given node in the value table.
uint16_t addSymbol(const Node &sym)
Register a given node in the symbol table.
void handleCalls(const Node &x, Page p, bool is_result_unused, bool is_terminal, const std::string &var_name)
static std::optional< Instruction > getListInstruction(const std::string &name) noexcept
Checking if a symbol is a list instruction.
std::vector< ValTableElem > m_values
void compileWhile(const Node &x, Page p)
static bool nodeProducesOutput(const Node &node)
std::vector< IR::Block > m_temp_pages
we need temporary code pages for some compilations passes
const std::vector< ValTableElem > & values() const noexcept
Return the value table pre-computed.
const std::vector< IR::Block > & intermediateRepresentation() const noexcept
Return the IR blocks (one per scope)
static bool isUnaryInst(Instruction inst) noexcept
Check if a given instruction is unary (takes only one argument)
void compileExpression(const Node &x, Page p, bool is_result_unused, bool is_terminal, const std::string &var_name="")
Compile an expression (a node) recursively.
std::vector< IR::Block > m_code_pages
void compileLetMutSet(Keyword n, const Node &x, Page p)
ASTLowerer(unsigned debug)
Construct a new ASTLowerer object.
std::vector< std::string > m_symbols
void compileFunction(const Node &x, Page p, bool is_result_unused, const std::string &var_name)
void compileListInstruction(const Node &c0, const Node &x, Page p, bool is_result_unused)
static void buildAndThrowError(const std::string &message, const Node &node)
Throw a nice error message.
static bool isTernaryInst(Instruction inst) noexcept
Check if a given instruction is ternary (takes three arguments)
LocalsLocator m_locals_locator
void compileSymbol(const Node &x, Page p, bool is_result_unused)
void compileIf(const Node &x, Page p, bool is_result_unused, bool is_terminal, const std::string &var_name)
static void warning(const std::string &message, const Node &node)
Display a warning message.
void process(const Node &ast)
Start the compilation.
static std::optional< uint16_t > getBuiltin(const std::string &name) noexcept
Checking if a symbol is a builtin.
void compilePluginImport(const Node &x, Page p)
static std::optional< Instruction > getOperator(const std::string &name) noexcept
Checking if a symbol is an operator.
IR::Block & page(const Page page) noexcept
helper functions to get a temp or finalized code page
const std::vector< std::string > & symbols() const noexcept
Return the symbol table pre-computed.
static Entity Label(label_t value)
Definition Entity.cpp:20
static Entity GotoIf(const Entity &label, bool cond)
Definition Entity.cpp:36
static Entity Goto(const Entity &label)
Definition Entity.cpp:28
void saveScopeLengthForBranch()
Save the current scope length before entering a branch, so that we can ignore variable definitions in...
std::optional< std::size_t > lookupLastScopeByName(const std::string &name)
Search for a local in the current scope. Returns std::nullopt in case of closure scopes or if the var...
void dropVarsForBranch()
Drop potentially defined variables in the last saved branch.
void deleteScope()
Delete the last scope.
void addLocal(const std::string &name)
Register a local in the current scope, triggered by a STORE instruction. If the local already exists,...
void createScope(ScopeType type=ScopeType::Default)
Create a new scope.
void traceStart(std::string &&trace_name)
Definition Logger.hpp:74
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
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
const std::vector< Node > & constList() const noexcept
Return the list of sub-nodes held by the node.
Definition Node.cpp:72
std::string repr() const noexcept
Compute a representation of the node without any comments or additional sugar, colors,...
Definition Node.cpp:160
const Namespace & constArkNamespace() const noexcept
Return the namespace held by the value (if the node type allows it)
Definition Node.cpp:57
std::size_t col() const noexcept
Get the column at which this node was created.
Definition Node.cpp:140
std::size_t line() const noexcept
Get the line at which this node was created.
Definition Node.cpp:135
ARK_API std::string makeContextWithNode(const std::string &message, const internal::Node &node)
Helper used by the compiler to generate a colorized context from a node.
ARK_API const std::vector< std::pair< std::string, Value > > builtins
constexpr std::array< std::string_view, 9 > listInstructions
Definition Common.hpp:115
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 Or
Definition Common.hpp:131
std::string typeToString(const Node &node) noexcept
Definition Node.hpp:240
Keyword
The different keywords available.
Definition Common.hpp:75
Instruction
The different bytecodes are stored here.
CodeError thrown by the compiler (parser, macro processor, optimizer, and compiler itself)
std::shared_ptr< Node > ast
Definition Namespace.hpp:18
A Compiler Value class helper to handle multiple types.