17 using namespace literals;
29 Pass(
"ASTLowerer", debug)
35 std::ranges::copy(constants, std::back_inserter(
m_values));
84 [&name](
const std::pair<std::string, Value>& element) ->
bool {
85 return name == element.first;
103 return node.
constList().front().string() ==
"breakpoint";
125 const std::string& name = node.
constList().front().string();
126 return name !=
"breakpoint";
135 case NOT: [[fallthrough]];
136 case LEN: [[fallthrough]];
137 case IS_EMPTY: [[fallthrough]];
138 case TAIL: [[fallthrough]];
139 case HEAD: [[fallthrough]];
140 case IS_NIL: [[fallthrough]];
141 case TO_NUM: [[fallthrough]];
142 case TO_STR: [[fallthrough]];
167 case ADD: [[fallthrough]];
168 case SUB: [[fallthrough]];
169 case MUL: [[fallthrough]];
190 const std::string invalid_node_msg =
"The given node doesn't return a value, and thus can't be used as an expression.";
195 buildAndThrowError(fmt::format(
"Invalid node ; if it was computed by a macro, check that a node is returned"), node);
199 buildAndThrowError(fmt::format(
"Invalid node inside call to `{}'. {}", additional_ctx, invalid_node_msg), node);
203 buildAndThrowError(fmt::format(
"Invalid node inside tail call to `{}'. {}", additional_ctx, invalid_node_msg), node);
207 buildAndThrowError(fmt::format(
"Invalid node inside call to operator `{}'. {}", additional_ctx, invalid_node_msg), node);
224 page(p).emplace_back(GET_FIELD, i);
233 if (!is_result_unused)
234 page(p).emplace_back(LOAD_CONST, i);
244 if (!is_result_unused)
246 static const std::optional<uint16_t> nil =
getBuiltin(
"nil");
247 page(p).emplace_back(BUILTIN, nil.value());
258 switch (
const Keyword keyword = head.keyword())
261 compileIf(x, p, is_result_unused, is_terminal, can_use_ref);
280 for (std::size_t i = 1, size = x.
list().size(); i < size; ++i)
283 const bool unused = is_result_unused || (ends_on_breakpoint ? i + 2 != size : i + 1 != size);
290 is_terminal && (ends_on_breakpoint ? i + 2 == size : i + 1 == size),
313 handleCalls(x, p, is_result_unused, is_terminal, can_use_ref);
319 "NodeType `{}' not handled in ASTLowerer::compileExpression. Please fill an issue on GitHub: https://github.com/ArkScript-lang/Ark",
326 const std::string& name = x.
string();
329 page(p).emplace_back(Instruction::BUILTIN, it_builtin.value());
331 buildAndThrowError(fmt::format(
"`{}' updates a list in-place, and can not be used as a value. Prefer using their copy alternative (without the `!` at the end) when possible", name), x);
333 buildAndThrowError(fmt::format(
"`{}' can not be used as a value like `+', where (let add +) (add 1 2) would be valid", name), x);
335 buildAndThrowError(fmt::format(
"Found a freestanding operator: `{}`. It can not be used as value like `+', where (let add +) (add 1 2) would be valid", name), x);
342 if (maybe_local_idx.has_value())
343 page(p).emplace_back(LOAD_FAST_BY_INDEX,
static_cast<uint16_t
>(maybe_local_idx.value())).setRelatedResourceId(symbol_id);
345 page(p).emplace_back(LOAD_FAST, symbol_id);
353 if (is_result_unused)
355 warning(
"Statement has no effect", x);
356 page(p).emplace_back(POP);
364 const std::string& name = head.
string();
368 const auto argc = x.
constList().size() - 1u;
370 if (argc < 2 && APPEND <= inst && inst <= SET_AT_2_INDEX)
371 buildAndThrowError(fmt::format(
"Can not use {} with less than 2 arguments", name), head);
374 if (argc != 2 && (inst == POP_LIST || inst == POP_LIST_IN_PLACE))
375 buildAndThrowError(fmt::format(
"Expected 2 arguments (list, index) for {}, got {}", name, argc), head);
376 if (argc != 3 && inst == SET_AT_INDEX)
377 buildAndThrowError(fmt::format(
"Expected 3 arguments (list, index, value) for {}, got {}", name, argc), head);
378 if (argc != 4 && inst == SET_AT_2_INDEX)
379 buildAndThrowError(fmt::format(
"Expected 4 arguments (list, y, x, value) for {}, got {}", name, argc), head);
382 for (std::size_t i = x.
constList().size() - 1u; i > 0; --i)
392 std::size_t inst_argc = 0;
401 case APPEND_IN_PLACE:
405 case CONCAT_IN_PLACE:
406 inst_argc = argc - 1;
417 case POP_LIST_IN_PLACE:
418 inst_argc = is_result_unused ? 0 : 1;
424 page(p).emplace_back(inst,
static_cast<uint16_t
>(inst_argc));
425 page(p).back().setSourceLocation(head.filename(), head.position().start.line);
427 if (!is_result_unused && (inst == APPEND_IN_PLACE || inst == CONCAT_IN_PLACE))
435 if (is_result_unused && (inst == LIST || inst == APPEND || inst == CONCAT || inst == POP_LIST))
437 warning(
"Ignoring return value of function", x);
438 page(p).emplace_back(POP);
445 const auto argc = x.
constList().size() - 1u;
448 buildAndThrowError(fmt::format(
"Expected 2 arguments (function, arguments) for apply, got {}", argc), head);
453 for (
Node& node : x.
list() | std::ranges::views::drop(1))
460 page(p).emplace_back(APPLY);
462 page(p).emplace_back(label_return);
464 if (is_result_unused)
465 page(p).emplace_back(POP);
471 buildAndThrowError(
"Invalid condition: missing 'cond' and 'then' nodes, expected (if cond then)", x);
483 bool created_vars =
false;
504 page(p).emplace_back(label_then);
511 page(p).emplace_back(label_end);
528 std::size_t capture_inst_count = 0;
529 for (
const auto& node : x.
constList()[1].constList())
533 const uint16_t symbol_id =
addSymbol(node);
537 if (
const auto& maybe_nqn = node.getUnqualifiedName(); maybe_nqn.has_value() && maybe_nqn.value() != node.string())
541 page(p).emplace_back(RENAME_NEXT_CAPTURE, nqn_id);
542 page(p).emplace_back(CAPTURE, symbol_id);
545 page(p).emplace_back(CAPTURE, symbol_id);
547 ++capture_inst_count;
550 const bool is_closure = capture_inst_count > 0;
557 std::optional<std::string> page_name = std::nullopt;
563 { .closure = is_closure,
564 .name = page_name });
565 bool mutate_at_least_one_arg =
false;
567 page(p).emplace_back(is_closure ? MAKE_CLOSURE : LOAD_CONST,
addValue(function_body_page.
index, x));
569 std::size_t arg_count = 0;
571 for (
const auto& node : x.
constList()[1].constList() | std::ranges::views::reverse)
575 page(function_body_page).emplace_back(STORE,
addSymbol(node));
579 mutate_at_least_one_arg = node.nodeType() ==
NodeType::MutArg || mutate_at_least_one_arg;
583 page(function_body_page).emplace_back(STORE_REF,
addSymbol(node));
604 page(function_body_page).emplace_back(RET);
608 if (is_result_unused)
610 warning(
"Unused declared function", x);
611 page(p).emplace_back(POP);
617 bool is_recursive =
false;
618 bool is_simple =
true;
622 if (e.inst() == TAIL_CALL_SELF || e.inst() == CALL_CURRENT_PAGE)
624 if (e.inst() == APPLY || e.inst() == CALL || e.inst() == CALL_SYMBOL || e.inst() == CALL_SYMBOL_BY_INDEX ||
625 e.inst() == MAKE_CLOSURE)
642 const std::string name = x.
constList()[1].string();
646 buildAndThrowError(
"Can not define a variable using the same name as the function it is defined inside. You need to rename the function or the variable", x);
648 const bool is_function = x.
constList()[2].isFunction();
651 std::size_t arg_count = 0;
655 for (
const auto& node : x.
constList()[2].constList()[1].constList())
662 x.
list()[2].setFunctionKind(
false);
671 page(p).emplace_back(STORE, i);
675 page(p).emplace_back(SET_VAL, i);
677 if (!is_result_unused)
678 page(p).emplace_back(LOAD_SYMBOL, i);
691 page(p).emplace_back(CREATE_SCOPE);
696 page(p).emplace_back(label_loop);
712 page(p).emplace_back(label_end);
714 page(p).emplace_back(POP_SCOPE);
722 for (std::size_t i = 0, end = package_node.
constList().size(); i < end; ++i)
724 path += package_node.
constList()[i].string();
733 page(p).emplace_back(PLUGIN,
id);
741 for (
Node& value : std::ranges::drop_view(call.
list(), 1))
764 bool matched =
false;
773 if (
const auto maybe_operator =
getOperator(node.
string()); maybe_operator.has_value())
776 if (maybe_operator.value() == BREAKPOINT)
777 is_result_unused =
false;
790 if (is_result_unused)
791 page(p).emplace_back(POP);
797 const auto name = node.
string();
804 "Expected at least 2 arguments while compiling '{}', got {}",
812 "Can not use `{}' inside a `{}' expression, as it doesn't return a value",
813 x.
list()[1].repr(), name),
819 page(p).emplace_back(shortcircuit_entity);
821 for (std::size_t i = 2, end = x.
constList().size(); i < end; ++i)
826 "Can not use `{}' inside a `{}' expression, as it doesn't return a value",
827 x.
list()[i].repr(), name),
831 page(p).emplace_back(shortcircuit_entity);
834 page(p).emplace_back(label_shortcircuit);
839 constexpr std::size_t start_index = 1;
844 std::size_t exp_count = 0;
845 for (std::size_t index = start_index, size = x.
constList().size(); index < size; ++index)
859 page(p).emplace_back(op);
865 buildAndThrowError(fmt::format(
"`{}' expected at most one argument, but was called with {}", op_name, exp_count), x.
constList()[0]);
866 page(p).emplace_back(op, exp_count);
872 page(p).emplace_back(op);
878 page(p).emplace_back(op);
880 else if (exp_count <= 1)
885 buildAndThrowError(fmt::format(
"`{}' requires 2 arguments, but got {}.", op_name, exp_count), x);
892 constexpr std::size_t start_index = 1;
896 std::size_t args_count = 0;
897 for (
auto it = x.
constList().begin() + start_index, it_end = x.
constList().end(); it != it_end; ++it)
907 if (
const std::size_t expected_arg_count =
m_opened_vars.top().argument_count; args_count != expected_arg_count)
909 std::vector<std::string> arg_names;
910 if (expected_arg_count > 0)
912 arg_names.reserve(expected_arg_count + 1);
913 arg_names.emplace_back(
"");
914 for (std::size_t i = 0; i < expected_arg_count; ++i)
915 arg_names.emplace_back(1,
static_cast<char>(
'a' + i));
920 "When performing tail-call `{}', received {} argument{}, but expected {}: `({}{})'",
923 args_count > 1 ?
"s" :
"",
926 fmt::join(arg_names,
" ")),
931 page(p).emplace_back(TAIL_CALL_SELF);
945 std::optional<uint16_t> call_arg = std::nullopt;
962 if (
page(proc_page).empty())
964 else if (
page(proc_page).back().inst() == GET_FIELD)
966 page(proc_page).back().replaceInstruction(GET_FIELD_AS_CLOSURE);
967 else if (
page(proc_page).size() == 1)
970 const uint16_t arg =
page(proc_page).back().primaryArg();
972 if (inst == LOAD_FAST)
977 page(proc_page).clear();
979 else if (inst == LOAD_FAST_BY_INDEX)
982 page(proc_page).clear();
988 page(proc_page).clear();
990 else if (inst == LOAD_CONST)
996 for (
const auto& inst :
page(proc_page))
997 page(p).push_back(inst);
1006 page(p).emplace_back(CALL, args_count).setRelatedResourceId(call_arg);
1010 page(p).emplace_back(CALL_CURRENT_PAGE,
addSymbol(node), args_count);
1014 assert(call_arg.has_value() &&
"Expected a value for call_arg with CallType::Symbol");
1015 page(p).emplace_back(CALL_SYMBOL, call_arg.value(), args_count).setRelatedResourceId(call_arg.value());
1022 assert(
page(temp_page).size() == 1 &&
page(temp_page).back().inst() == LOAD_FAST_BY_INDEX);
1023 page(p).emplace_back(CALL_SYMBOL_BY_INDEX,
page(temp_page).back().primaryArg(), args_count).setRelatedResourceId(
page(temp_page).back().relatedResourceId());
1029 assert(call_arg.has_value() &&
"Expected a value for call_arg with CallType::Builtin");
1030 page(p).emplace_back(CALL_BUILTIN, call_arg.value(), args_count);
1036 page(p).emplace_back(label_return);
1047 it =
m_symbols.begin() +
static_cast<std::vector<std::string>::difference_type
>(
m_symbols.size() - 1);
1050 const auto distance = std::distance(
m_symbols.begin(), it);
1052 return static_cast<uint16_t
>(distance);
1059 auto it = std::ranges::find(
m_values, v);
1063 it =
m_values.begin() +
static_cast<std::vector<ValTableElem>::difference_type
>(
m_values.size() - 1);
1066 const auto distance = std::distance(
m_values.begin(), it);
1068 return static_cast<uint16_t
>(distance);
1075 auto it = std::ranges::find(
m_values, v);
1079 it =
m_values.begin() +
static_cast<std::vector<ValTableElem>::difference_type
>(
m_values.size() - 1);
1082 const auto distance = std::distance(
m_values.begin(), it);
1084 return static_cast<uint16_t
>(distance);
Host the declaration of all the ArkScript builtins.
Tools to report code errors nicely to the user.
ArkScript homemade exceptions.
User defined literals for Ark internals.
const String_t & string() const
uint16_t addValue(const Node &x)
Register a given node in the value table.
IR::Block::vec_t & page(const Page page) noexcept
helper functions to get a temp or finalised code page
void pushFunctionCallArguments(Node &call, Page p, bool is_tail_call)
bool handleFunctionCall(Node &x, Page p, bool is_terminal)
uint16_t addSymbol(const Node &sym)
Register a given node in the symbol table.
IR::Block & block(const Page page) noexcept
static std::optional< Instruction > getListInstruction(const std::string &name) noexcept
Checking if a symbol is a list instruction.
std::vector< ValTableElem > m_values
std::stack< Var > m_opened_vars
stack of vars we are currently declaring
void handleShortcircuit(Node &x, Page p, bool can_use_ref)
void compileListInstruction(Node &x, Page p, bool is_result_unused)
static bool nodeProducesOutput(const Node &node)
std::vector< IR::Block > m_temp_pages
we need temporary code pages for some compilations passes
static bool isRepeatableOperation(Instruction inst) noexcept
Check if an operator can be repeated.
const std::vector< ValTableElem > & values() const noexcept
Return the value table pre-computed.
void process(Node &ast)
Start the compilation.
void offsetPagesBy(std::size_t offset)
Start bytecode pages at a given offset (by default, 0)
void compileExpression(Node &x, Page p, bool is_result_unused, bool is_terminal, bool can_use_ref)
Compile an expression (a node) recursively.
const std::vector< IR::Block > & intermediateRepresentation() const noexcept
Return the IR blocks (one per scope)
static bool isBreakpoint(const Node &node)
static bool isUnaryInst(Instruction inst) noexcept
Check if a given instruction is unary (takes only one argument)
void compileLetMutSet(Keyword n, Node &x, Page p, bool is_result_unused)
IR::label_t m_current_label
Page createNewCodePage(PageCreationData &&args=PageCreationData {}) noexcept
std::vector< IR::Block > m_code_pages
ASTLowerer(unsigned debug)
Construct a new ASTLowerer object.
static void makeError(ErrorKind kind, const Node &node, const std::string &additional_ctx)
Throw a nice error message, using a message builder.
@ InvalidNodeInTailCallNoReturnValue
@ InvalidNodeNoReturnValue
@ InvalidNodeInOperatorNoReturnValue
void compileWhile(Node &x, Page p)
void handleOperator(Node &x, Page p, Instruction op)
void compileApplyInstruction(Node &x, Page p, bool is_result_unused)
std::vector< std::string > m_symbols
void handleCalls(Node &x, Page p, bool is_result_unused, bool is_terminal, bool can_use_ref)
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, bool can_use_ref)
void warning(const std::string &message, const Node &node)
Display a warning message.
bool isFunctionCallingItself(const std::string &name) noexcept
Check if we are in a recursive self call.
void compileFunction(Node &x, Page p, bool is_result_unused)
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)
std::size_t m_start_page_at_offset
Used to offset the page numbers when compiling code in the debugger.
static std::optional< Instruction > getOperator(const std::string &name) noexcept
Checking if a symbol is an operator.
const std::vector< std::string > & symbols() const noexcept
Return the symbol table pre-computed.
void setFunctionMetadata(Page p, std::size_t arg_count, bool mutates_args)
void addToTables(const std::vector< std::string > &symbols, const std::vector< ValTableElem > &constants)
Pre-fill tables (used by the debugger)
void compileIf(Node &x, Page p, bool is_result_unused, bool is_terminal, bool can_use_ref)
static Entity Goto(const Entity &label, Instruction inst=Instruction::JUMP)
Create a new Goto IR Entity.
static Entity Label(label_t value)
Create a new Label IR Entity.
static Entity GotoIf(const Entity &label, bool cond)
Create a new Goto IR Entity.
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...
bool 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 markLastLocalAsUnreachable()
Mark the last variable of a scope as unreachable, blocking lookupLastScopeByName(....
bool colorize() const noexcept
Check if logs can be colorized.
void warn(const char *fmt, Args &&... args)
Write a warn level log using fmtlib.
void traceStart(std::string &&trace_name)
A node of an Abstract Syntax Tree for ArkScript.
NodeType nodeType() const noexcept
Return the node type.
bool isAnonymousFunction() const noexcept
Check if a node is an anonymous function.
const std::string & filename() const noexcept
Return the filename in which this node was created.
const std::string & string() const noexcept
Return the string held by the value (if the node type allows it)
const std::vector< Node > & constList() const noexcept
Return the list of sub-nodes held by the node.
std::string repr() const noexcept
Compute a representation of the node without any comments or additional sugar, colors,...
FileSpan position() const noexcept
Get the span of the node (start and end)
const Namespace & constArkNamespace() const noexcept
Return the namespace held by the value (if the node type allows it)
std::vector< Node > & list() noexcept
Return the list of sub-nodes held by the node.
An interface to describe compiler passes.
std::string makeContextWithNode(const std::string &message, const internal::Node &node, bool colorize=true)
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::string_view AnonymousBlockName
constexpr std::array< std::string_view, 9 > listInstructions
constexpr std::string_view Apply
constexpr std::array< std::string_view, 24 > operators
constexpr std::string_view And
constexpr std::array UpdateRef
All the builtins that modify in place a variable.
constexpr std::string_view Or
ARK_ALWAYS_INLINE std::string typeToString(const Node &node) noexcept
constexpr uint8_t FirstOperator
Keyword
The different keywords available.
Instruction
The different bytecodes are stored here.
constexpr uint16_t MaxValue16Bits
CodeError thrown by the compiler (parser, macro processor, optimizer, and compiler itself)
std::size_t line
0-indexed line number
struct Ark::internal::IR::Block::Metadata metadata
std::shared_ptr< Node > ast
A Compiler Value class helper to handle multiple types.