ArkScript
A small, lisp-inspired, functional scripting language
ASTLowerer.hpp
Go to the documentation of this file.
1/**
2 * @file ASTLowerver.hpp
3 * @author Lexy Plateau (lexplt.dev@gmail.com)
4 * @brief ArkScript compiler is in charge of transforming the AST into IR
5 * @date 2020-10-27
6 *
7 * @copyright Copyright (c) 2020-2026
8 *
9 */
10
11#ifndef ARK_COMPILER_LOWERER_ASTLOWERER_HPP
12#define ARK_COMPILER_LOWERER_ASTLOWERER_HPP
13
14#include <stack>
15#include <vector>
16#include <string>
17#include <cinttypes>
18#include <optional>
19
21#include <Ark/Utils/Logger.hpp>
22#include <Ark/Compiler/Pass.hpp>
28
29namespace Ark
30{
31 class State;
32 class Welder;
33}
34
35namespace Ark::internal
36{
38 {
39 bool temp { false };
40 bool closure { false };
41 std::optional<std::string> name { std::nullopt };
42 };
43
44 /**
45 * @brief The ArkScript AST to IR compiler
46 *
47 */
48 class ARK_API ASTLowerer final : public Pass
49 {
50 public:
51 /**
52 * @brief Construct a new ASTLowerer object
53 *
54 * @param debug the debug level
55 */
56 explicit ASTLowerer(unsigned debug);
57
58 /**
59 * @brief Pre-fill tables (used by the debugger)
60 *
61 * @param symbols
62 * @param constants
63 */
64 void addToTables(const std::vector<std::string>& symbols, const std::vector<ValTableElem>& constants);
65
66 /**
67 * @brief Start bytecode pages at a given offset (by default, 0)
68 *
69 * @param offset
70 */
71 void offsetPagesBy(std::size_t offset);
72
73 /**
74 * @brief Start the compilation
75 *
76 * @param ast
77 */
78 void process(Node& ast);
79
80 /**
81 * @brief Return the IR blocks (one per scope)
82 *
83 * @return const std::vector<Block>&
84 */
85 [[nodiscard]] const std::vector<IR::Block>& intermediateRepresentation() const noexcept;
86
87 /**
88 * @brief Return the symbol table pre-computed
89 *
90 * @return const std::vector<std::string>&
91 */
92 [[nodiscard]] const std::vector<std::string>& symbols() const noexcept;
93
94 /**
95 * @brief Return the value table pre-computed
96 *
97 * @return const std::vector<ValTableElem>&
98 */
99 [[nodiscard]] const std::vector<ValTableElem>& values() const noexcept;
100
101 [[nodiscard]] IR::label_t lastLabel() const noexcept
102 {
103 return m_current_label;
104 }
105
106 private:
107 struct Page
108 {
109 std::size_t index;
111 };
112
113 struct Var
114 {
115 std::string name;
116 std::size_t argument_count;
117 };
118
120
121 // tables: symbols, values, plugins and codes
122 std::vector<std::string> m_symbols;
123 std::vector<ValTableElem> m_values;
124 std::size_t m_start_page_at_offset = 0; ///< Used to offset the page numbers when compiling code in the debugger
125 std::vector<IR::Block> m_code_pages;
126 std::vector<IR::Block> m_temp_pages; ///< we need temporary code pages for some compilations passes
127 IR::label_t m_current_label = 0;
128 std::stack<Var> m_opened_vars; ///< stack of vars we are currently declaring
129
130 enum class ErrorKind
131 {
132 InvalidNodeMacro,
133 InvalidNodeNoReturnValue,
134 InvalidNodeInOperatorNoReturnValue,
135 InvalidNodeInTailCallNoReturnValue
136 };
137
139 {
140 if (!args.temp)
141 {
142 const std::size_t new_page_addr = m_start_page_at_offset + m_code_pages.size();
143 m_code_pages.emplace_back(
145 .name = args.name,
146 .argument_count = 0,
147 .addr = new_page_addr,
148 .is_closure = args.closure },
150 return Page { .index = new_page_addr, .is_temp = false };
151 }
152
153 m_temp_pages.emplace_back();
154 return Page { .index = m_temp_pages.size() - 1u, .is_temp = true };
155 }
156
157 IR::Block& block(const Page page) noexcept
158 {
159 if (!page.is_temp)
160 return m_code_pages[page.index - m_start_page_at_offset];
161 return m_temp_pages[page.index];
162 }
163
164 /**
165 * @brief helper functions to get a temp or finalised code page
166 *
167 * @param page page descriptor
168 * @return std::vector<IR::Entity>&
169 */
170 IR::Block::vec_t& page(const Page page) noexcept
171 {
172 if (!page.is_temp)
173 return m_code_pages[page.index - m_start_page_at_offset].data;
174 return m_temp_pages[page.index].data;
175 }
176
177 /**
178 * @brief Check if we are in a recursive self call
179 *
180 * @param name symbol name being compiled
181 * @return true if the name passed is the name of the last function we entered
182 */
183 [[nodiscard]] bool isFunctionCallingItself(const std::string& name) noexcept
184 {
185 return !m_opened_vars.empty() && m_opened_vars.top().name == name;
186 }
187
188 /**
189 * @brief Checking if a symbol is an operator
190 *
191 * @param name symbol name
192 * @return std::optional<Instruction> operator instruction
193 */
194 static std::optional<Instruction> getOperator(const std::string& name) noexcept;
195
196 /**
197 * @brief Checking if a symbol is a builtin
198 *
199 * @param name symbol name
200 * @return std::optional<uint16_t> builtin number
201 */
202 static std::optional<uint16_t> getBuiltin(const std::string& name) noexcept;
203
204 /**
205 * @brief Checking if a symbol is a list instruction
206 *
207 * @param name
208 * @return std::optional<Instruction> list instruction
209 */
210 static std::optional<Instruction> getListInstruction(const std::string& name) noexcept;
211
212 /**
213 * Checks if a node is a list and is a call to 'breakpoint'
214 * @param node node to check
215 * @return true if the node is a 'breakpoint' call: (breakpoint <cond>)
216 * @return false otherwise
217 */
218 static bool isBreakpoint(const Node& node);
219
220 /**
221 * Checks if a node is a list and has a keyboard as its first node, indicating if it's producing a value on the stack or not
222 * @param node node to check
223 * @return true if the node produces an output on the stack (fun, if, begin)
224 * @return false otherwise (let, mut, set, while, import, del)
225 */
226 static bool nodeProducesOutput(const Node& node);
227
228 /**
229 * @brief Check if a given instruction is unary (takes only one argument)
230 *
231 * @param inst
232 * @return true the instruction is unary, false otherwise
233 */
234 static bool isUnaryInst(Instruction inst) noexcept;
235
236 /**
237 * @brief Check if a given instruction is ternary (takes three arguments)
238 *
239 * @param inst
240 * @return true the instruction is ternary, false otherwise
241 */
242 static bool isTernaryInst(Instruction inst) noexcept;
243
244 /**
245 * @brief Check if an operator can be repeated
246 *
247 * @param inst
248 * @return true the instruction can be repeated, eg (+ 1 2 3) compiles to (+ (+ 1 2) 3), false otherwise
249 */
250 static bool isRepeatableOperation(Instruction inst) noexcept;
251
252 /**
253 * @brief Display a warning message
254 *
255 * @param message
256 * @param node
257 */
258 void warning(const std::string& message, const Node& node);
259
260 /**
261 * @brief Throw a nice error message
262 *
263 * @param message
264 * @param node
265 */
266 [[noreturn]] static void buildAndThrowError(const std::string& message, const Node& node);
267
268 /**
269 * @brief Throw a nice error message, using a message builder
270 *
271 * @param kind error kind
272 * @param node erroneous node
273 * @param additional_ctx optional context for the error, e.g. the macro name
274 */
275 static void makeError(ErrorKind kind, const Node& node, const std::string& additional_ctx);
276
277 /**
278 * @brief Compile an expression (a node) recursively
279 *
280 * @param x the Node to compile
281 * @param p the current page number we're on
282 * @param is_result_unused
283 * @param is_terminal
284 * @param can_use_ref
285 */
286 void compileExpression(Node& x, Page p, bool is_result_unused, bool is_terminal, bool can_use_ref);
287
288 void compileSymbol(const Node& x, Page p, bool is_result_unused, bool can_use_ref);
289 void compileListInstruction(Node& x, Page p, bool is_result_unused);
290 void compileApplyInstruction(Node& x, Page p, bool is_result_unused);
291 void compileIf(Node& x, Page p, bool is_result_unused, bool is_terminal, bool can_use_ref);
292 void compileFunction(Node& x, Page p, bool is_result_unused);
293 void setFunctionMetadata(Page p, std::size_t arg_count, bool mutates_args);
294 void compileLetMutSet(Keyword n, Node& x, Page p, bool is_result_unused);
295 void compileWhile(Node& x, Page p);
296 void compilePluginImport(const Node& x, Page p);
297 void pushFunctionCallArguments(Node& call, Page p, bool is_tail_call);
298 void handleCalls(Node& x, Page p, bool is_result_unused, bool is_terminal, bool can_use_ref);
299 void handleShortcircuit(Node& x, Page p, bool can_use_ref);
300 void handleOperator(Node& x, Page p, Instruction op);
301 bool handleFunctionCall(Node& x, Page p, bool is_terminal);
302
303 /**
304 * @brief Register a given node in the symbol table
305 * @details Can throw if the table is full
306 *
307 * @param sym
308 * @return uint16_t
309 */
310 uint16_t addSymbol(const Node& sym);
311
312 /**
313 * @brief Register a given node in the value table
314 * @details Can throw if the table is full
315 *
316 * @param x
317 * @return uint16_t
318 */
319 uint16_t addValue(const Node& x);
320
321 /**
322 * @brief Register a page id (function reference) in the value table
323 * @details Can throw if the table is full
324 *
325 * @param page_id
326 * @param current A reference to the current node, for context
327 * @return std::size_t
328 */
329 uint16_t addValue(std::size_t page_id, const Node& current);
330 };
331}
332
333#endif
An entity in the IR is a bundle of information.
The different instructions used by the compiler and virtual machine.
Track locals at compile.
Internal logger.
#define ARK_API
Definition Module.hpp:22
AST node used by the parser, optimizer and compiler.
Interface for a compiler pass.
ArkScript configuration macros.
The basic value type handled by the compiler.
The ArkScript AST to IR compiler.
IR::Block::vec_t & page(const Page page) noexcept
helper functions to get a temp or finalised code page
IR::Block & block(const Page page) noexcept
std::vector< ValTableElem > m_values
std::stack< Var > m_opened_vars
stack of vars we are currently declaring
std::vector< IR::Block > m_temp_pages
we need temporary code pages for some compilations passes
IR::label_t lastLabel() const noexcept
Page createNewCodePage(PageCreationData &&args=PageCreationData {}) noexcept
std::vector< IR::Block > m_code_pages
std::vector< std::string > m_symbols
LocalsLocator m_locals_locator
bool isFunctionCallingItself(const std::string &name) noexcept
Check if we are in a recursive self call.
A node of an Abstract Syntax Tree for ArkScript.
Definition Node.hpp:32
An interface to describe compiler passes.
Definition Pass.hpp:24
std::size_t label_t
Definition Entity.hpp:35
Keyword
The different keywords available.
Definition Common.hpp:79
Instruction
The different bytecodes are stored here.
Block of IR entities, with attached metadata.
Definition Entity.hpp:241
std::vector< Entity > vec_t
Definition Entity.hpp:242
std::optional< std::string > name