16 using namespace literals;
28 Pass(
"ASTLowerer", debug)
34 std::ranges::copy(constants, std::back_inserter(
m_values));
82 [&name](
const std::pair<std::string, Value>& element) ->
bool {
83 return name == element.first;
101 return node.
constList().front().string() ==
"breakpoint";
123 const std::string& name = node.
constList().front().string();
124 return name !=
"breakpoint";
133 case NOT: [[fallthrough]];
134 case LEN: [[fallthrough]];
136 case TAIL: [[fallthrough]];
137 case HEAD: [[fallthrough]];
138 case IS_NIL: [[fallthrough]];
139 case TO_NUM: [[fallthrough]];
140 case TO_STR: [[fallthrough]];
165 case ADD: [[fallthrough]];
166 case SUB: [[fallthrough]];
167 case MUL: [[fallthrough]];
188 const std::string invalid_node_msg =
"The given node doesn't return a value, and thus can't be used as an expression.";
193 buildAndThrowError(fmt::format(
"Invalid node ; if it was computed by a macro, check that a node is returned"), node);
197 buildAndThrowError(fmt::format(
"Invalid node inside call to `{}'. {}", additional_ctx, invalid_node_msg), node);
201 buildAndThrowError(fmt::format(
"Invalid node inside tail call to `{}'. {}", additional_ctx, invalid_node_msg), node);
205 buildAndThrowError(fmt::format(
"Invalid node inside call to operator `{}'. {}", additional_ctx, invalid_node_msg), node);
231 if (!is_result_unused)
242 if (!is_result_unused)
244 static const std::optional<uint16_t> nil =
getBuiltin(
"nil");
256 switch (
const Keyword keyword = head.keyword())
259 compileIf(x, p, is_result_unused, is_terminal);
276 for (std::size_t i = 1, size = x.
list().size(); i < size; ++i)
281 (i != size - 1) || is_result_unused,
283 is_terminal && (i == size - 1));
311 "NodeType `{}' not handled in ASTLowerer::compileExpression. Please fill an issue on GitHub: https://github.com/ArkScript-lang/Ark",
318 const std::string& name = x.
string();
323 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);
329 if (maybe_local_idx.has_value())
340 if (is_result_unused)
342 warning(
"Statement has no effect", x);
351 const std::string& name = head.
string();
355 const auto argc = x.
constList().size() - 1u;
358 buildAndThrowError(fmt::format(
"Can not use {} with less than 2 arguments", name), head);
362 buildAndThrowError(fmt::format(
"Expected 3 arguments (list, index, value) for {}, got {}", name, argc), head);
364 buildAndThrowError(fmt::format(
"Expected 4 arguments (list, y, x, value) for {}, got {}", name, argc), head);
367 for (std::size_t i = x.
constList().size() - 1u; i > 0; --i)
377 std::size_t inst_argc = 0;
391 inst_argc = argc - 1;
403 inst_argc = is_result_unused ? 0 : 1;
409 page(p).emplace_back(inst,
static_cast<uint16_t
>(inst_argc));
410 page(p).back().setSourceLocation(head.filename(), head.position().start.line);
422 warning(
"Ignoring return value of function", x);
430 const auto argc = x.
constList().size() - 1u;
433 buildAndThrowError(fmt::format(
"Expected 2 arguments (function, arguments) for apply, got {}", argc), head);
438 for (
Node& node : x.
list() | std::ranges::views::drop(1))
447 page(p).emplace_back(label_return);
449 if (is_result_unused)
456 buildAndThrowError(
"Invalid condition: missing 'cond' and 'then' nodes, expected (if cond then)", x);
482 page(p).emplace_back(label_then);
489 page(p).emplace_back(label_end);
500 std::size_t capture_inst_count = 0;
501 for (
const auto& node : x.
constList()[1].constList())
505 const uint16_t symbol_id =
addSymbol(node);
509 if (
const auto& maybe_nqn = node.getUnqualifiedName(); maybe_nqn.has_value() && maybe_nqn.value() != node.string())
519 ++capture_inst_count;
522 const bool is_closure = capture_inst_count > 0;
535 for (
const auto& node : x.
constList()[1].constList() | std::ranges::views::reverse)
561 page(function_body_page).emplace_back(
RET);
565 if (is_result_unused)
567 warning(
"Unused declared function", x);
579 const std::string name = x.
constList()[1].string();
583 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);
585 const bool is_function = x.
constList()[2].isFunction();
589 x.
list()[2].setFunctionKind(
false);
604 if (!is_result_unused)
623 page(p).emplace_back(label_loop);
639 page(p).emplace_back(label_end);
649 for (std::size_t i = 0, end = package_node.
constList().size(); i < end; ++i)
651 path += package_node.
constList()[i].string();
668 for (
Node& value : std::ranges::drop_view(call.
list(), 1))
686 bool matched =
false;
695 if (
const auto maybe_operator =
getOperator(node.
string()); maybe_operator.has_value())
699 is_result_unused =
false;
712 if (is_result_unused)
719 const auto name = node.
string();
726 "Expected at least 2 arguments while compiling '{}', got {}",
734 "Can not use `{}' inside a `{}' expression, as it doesn't return a value",
735 x.
list()[1].repr(), name),
741 page(p).emplace_back(shortcircuit_entity);
743 for (std::size_t i = 2, end = x.
constList().size(); i < end; ++i)
748 "Can not use `{}' inside a `{}' expression, as it doesn't return a value",
749 x.
list()[i].repr(), name),
753 page(p).emplace_back(shortcircuit_entity);
756 page(p).emplace_back(label_shortcircuit);
761 constexpr std::size_t start_index = 1;
767 std::size_t exp_count = 0;
768 for (std::size_t index = start_index, size = x.
constList().size(); index < size; ++index)
782 page(p).emplace_back(op);
788 buildAndThrowError(fmt::format(
"`{}' expected at most one argument, but was called with {}", op_name, exp_count), x.
constList()[0]);
789 page(p).emplace_back(op, exp_count);
795 page(p).emplace_back(op);
801 page(p).emplace_back(op);
803 else if (exp_count <= 1)
808 buildAndThrowError(fmt::format(
"`{}' requires 2 arguments, but got {}.", op_name, exp_count), x);
815 constexpr std::size_t start_index = 1;
837 std::optional<uint16_t> call_arg = std::nullopt;
854 if (
page(proc_page).empty())
859 else if (
page(proc_page).size() == 1)
862 const uint16_t arg =
page(proc_page).back().primaryArg();
869 page(proc_page).clear();
874 page(proc_page).clear();
880 page(proc_page).clear();
886 for (
const auto& inst :
page(proc_page))
887 page(p).push_back(inst);
893 std::size_t args_count = 0;
894 for (
auto it = x.
constList().begin() + start_index, it_end = x.
constList().end(); it != it_end; ++it)
904 page(p).emplace_back(
CALL, args_count);
912 assert(call_arg.has_value() &&
"Expected a value for call_arg with CallType::Symbol");
927 assert(call_arg.has_value() &&
"Expected a value for call_arg with CallType::Builtin");
934 page(p).emplace_back(label_return);
945 it =
m_symbols.begin() +
static_cast<std::vector<std::string>::difference_type
>(
m_symbols.size() - 1);
948 const auto distance = std::distance(
m_symbols.begin(), it);
950 return static_cast<uint16_t
>(distance);
957 auto it = std::ranges::find(
m_values, v);
961 it =
m_values.begin() +
static_cast<std::vector<ValTableElem>::difference_type
>(
m_values.size() - 1);
964 const auto distance = std::distance(
m_values.begin(), it);
966 return static_cast<uint16_t
>(distance);
973 auto it = std::ranges::find(
m_values, v);
977 it =
m_values.begin() +
static_cast<std::vector<ValTableElem>::difference_type
>(
m_values.size() - 1);
980 const auto distance = std::distance(
m_values.begin(), it);
982 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.
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.
static std::optional< Instruction > getListInstruction(const std::string &name) noexcept
Checking if a symbol is a list instruction.
std::vector< ValTableElem > m_values
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.
void handleCalls(Node &x, Page p, bool is_result_unused, bool is_terminal)
Page createNewCodePage(const bool temp=false) noexcept
const std::vector< ValTableElem > & values() const noexcept
Return the value table pre-computed.
void compileIf(Node &x, Page p, bool is_result_unused, bool is_terminal)
void process(Node &ast)
Start the compilation.
void offsetPagesBy(std::size_t offset)
Start bytecode pages at a given offset (by default, 0)
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
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
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)
void compileExpression(Node &x, Page p, bool is_result_unused, bool is_terminal)
Compile an expression (a node) recursively.
LocalsLocator m_locals_locator
std::stack< std::string > m_opened_vars
stack of vars we are currently declaring
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.
void handleShortcircuit(Node &x, Page p)
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.
void addToTables(const std::vector< std::string > &symbols, const std::vector< ValTableElem > &constants)
Pre-fill tables (used by the debugger)
static Entity Goto(const Entity &label, Instruction inst=Instruction::JUMP)
static Entity Label(label_t value)
static Entity GotoIf(const Entity &label, bool cond)
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.
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::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::string_view Or
std::string typeToString(const Node &node) noexcept
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
std::shared_ptr< Node > ast
A Compiler Value class helper to handle multiple types.