ArkScript
A small, lisp-inspired, functional scripting language
Formatter.cpp
Go to the documentation of this file.
1#include <Ark/Constants.hpp>
2#include <CLI/Formatter.hpp>
3
4#include <fmt/core.h>
5
6#include <Ark/Utils/Files.hpp>
11
12using namespace Ark;
13using namespace Ark::internal;
14using namespace Ark::literals;
15
16Formatter::Formatter(const bool dry_run) :
17 m_dry_run(dry_run), m_parser(/* debug= */ 0, ParserMode::Raw), m_updated(false), m_logger("formatter", 0)
18{}
19
20Formatter::Formatter(std::string filename, const bool dry_run) :
21 m_filename(std::move(filename)), m_dry_run(dry_run), m_parser(/* debug= */ 0, ParserMode::Raw), m_updated(false), m_logger("formatter", 0)
22{}
23
25{
26 try
27 {
28 const std::string code = Utils::readFile(m_filename);
32
33 m_updated = code != m_output;
34 }
35 catch (const CodeError& e)
36 {
38 }
39}
40
41void Formatter::runWithString(const std::string& code)
42{
43 try
44 {
48
49 m_updated = code != m_output;
50 }
51 catch (const CodeError& e)
52 {
54 }
55}
56
57const std::string& Formatter::output() const
58{
59 return m_output;
60}
61
63{
64 return m_updated;
65}
66
68{
69 // remove useless surrounding begin (generated by the parser)
70 if (isBeginBlock(ast))
71 {
72 for (std::size_t i = 1, end = ast.constList().size(); i < end; ++i)
73 {
74 const Node node = ast.constList()[i];
75 if (shouldAddNewLineBetweenNodes(ast, i) && !m_output.empty())
76 m_output += "\n";
77 m_output += format(node, 0, false) + "\n";
78 }
79 }
80 else
81 m_output = format(ast, 0, false);
82
83 if (!m_dry_run)
84 {
85 std::ofstream stream(m_filename);
86 stream << m_output;
87 }
88}
89
90void Formatter::warnIfCommentsWereRemoved(const std::string& original_code, const std::string& filename)
91{
92 const std::size_t before_count = std::ranges::count(original_code, '#');
93 const std::size_t after_count = std::ranges::count(m_output, '#');
94
95 if (before_count != after_count)
96 {
98 "one or more comments from the original source code seem to have been {} by mistake while formatting {}",
99 before_count > after_count ? "removed" : "duplicated",
100 filename != ARK_NO_NAME_FILE ? filename : "file");
101 m_logger.warn("Please fill an issue on GitHub: https://github.com/ArkScript-lang/Ark");
102 }
103}
104
105bool Formatter::isListStartingWithKeyword(const Node& node, const Keyword keyword)
106{
107 return node.isListLike() && !node.constList().empty() && node.constList()[0].nodeType() == NodeType::Keyword && node.constList()[0].keyword() == keyword;
108}
109
111{
112 return isListStartingWithKeyword(node, Keyword::Begin);
113}
114
115bool Formatter::isFuncDef(const Node& node)
116{
117 return isListStartingWithKeyword(node, Keyword::Fun);
118}
119
121{
122 return node.isListLike() && !node.constList().empty() && node.constList()[0].nodeType() == NodeType::Symbol;
123}
124
125std::size_t Formatter::lineOfLastNodeIn(const Node& node)
126{
127 if (node.isListLike() && !node.constList().empty())
128 {
129 const std::size_t child_line = lineOfLastNodeIn(node.constList().back());
130 if (child_line < node.position().start.line)
131 return node.position().start.line;
132 return child_line;
133 }
134 return node.position().start.line;
135}
136
138{
139 const std::string formatted = format(node, 0, false);
140 const std::size_t max_len =
141 std::ranges::max(
142 Utils::splitString(formatted, '\n'),
143 [](const std::string& lhs, const std::string& rhs) {
144 return lhs.size() < rhs.size();
145 })
146 .size();
147 const std::size_t newlines = std::ranges::count(formatted, '\n');
148
149 // split on multiple lines if we have a very long node,
150 // or if we added many line breaks while doing dumb formatting
151 return max_len >= FormatterConfig::LongLineLength || (newlines > 0 && node.isListLike() && newlines + 1 >= node.constList().size());
152}
153
154bool Formatter::shouldSplitOnNewline(const Node& node, const bool on_multiple_lines)
155{
156 if (node.comment().empty() && isBeginBlock(node))
157 return false;
158 // If we have a function call that isn't on multiple lines,
159 // or a function call that is special (eg a switch, dict or list),
160 // no need to add a new line before the node.
161 // e.g. this is on multiple lines, but we have a special call:
162 // (let a (switch day
163 // 1 nil
164 // 2 true))
165 if (node.comment().empty() && isFuncCall(node) && (!on_multiple_lines || callKind(node) != CallKind::Nothing))
166 return false;
167 return on_multiple_lines ||
168 isLongLine(node) ||
169 (node.isListLike() && node.constList().size() > 1) ||
170 !node.comment().empty();
171}
172
173bool Formatter::shouldAddNewLineBetweenNodes(const Node& node, const std::size_t at)
174{
175 if (at <= 1)
176 return false;
177
178 const auto& list = node.constList();
179 const std::size_t previous_line = lineOfLastNodeIn(list[at - 1]);
180
181 const auto& child = list[at];
182
183 // If we have a node before the current one,
184 // and the line count between the two nodes is more than 1,
185 // maybe we should add a new line to preserve user spacing.
186 // However, if the current node has a comment, do not add a new line, this is causing the spacing.
187 if (child.position().start.line - previous_line > 1 && child.comment().empty())
188 return true;
189 // If we do have a comment but the spacing is more than 2,
190 // then add a newline to preserve user spacing.
191 if (child.position().start.line - previous_line > 2 && !child.comment().empty())
192 return true;
193 return false;
194}
195
197{
198 if (!node.constList().empty() && node.constList().front().nodeType() == NodeType::Symbol)
199 {
200 const auto& sym = node.constList().front().string();
201 if (sym == "list")
202 return CallKind::List;
203 if (sym == "dict")
204 return CallKind::Dict;
205 if (sym == "switch")
206 return CallKind::Switch;
207 }
208
209 return CallKind::Nothing;
210}
211
212std::string Formatter::format(const Node& node, std::size_t indent, bool after_newline)
213{
214 std::string result;
215 if (!node.comment().empty())
216 {
217 result += formatComment(node.comment(), indent);
218 after_newline = true;
219 }
220 if (after_newline)
221 result += prefix(indent);
222
223 switch (node.nodeType())
224 {
225 case NodeType::Symbol:
226 result += node.string();
227 break;
228 case NodeType::MutArg:
229 result += fmt::format("(mut {})", node.string());
230 break;
231 case NodeType::RefArg:
232 result += fmt::format("(ref {})", node.string());
233 break;
234 case NodeType::Capture:
235 result += "&" + node.string();
236 break;
237 case NodeType::Keyword:
238 result += std::string(keywords[static_cast<std::size_t>(node.keyword())]);
239 break;
240 case NodeType::String:
241 result += fmt::format("{}\"{}\"", node.isRawString() ? "r" : "", node.string());
242 break;
243 case NodeType::Number:
244 result += fmt::format("{}", node.number());
245 break;
246 case NodeType::List:
247 result += formatBlock(node, indent, after_newline);
248 break;
249 case NodeType::Spread:
250 result += fmt::format("...{}", node.string());
251 break;
252 case NodeType::Field:
253 {
254 std::string field = format(node.constList()[0], indent, false);
255 for (std::size_t i = 1, end = node.constList().size(); i < end; ++i)
256 field += "." + format(node.constList()[i], indent, false);
257 result += field;
258 break;
259 }
260 case NodeType::Macro:
261 result += formatMacro(node, indent);
262 break;
263 // not handling Namespace nor Unused node types as those can not be generated by the parser
264 case NodeType::Namespace:
265 [[fallthrough]];
266 case NodeType::Unused:
267 break;
268 }
269
270 if (!node.commentAfter().empty())
271 result += " " + formatComment(node.commentAfter(), /* indent= */ 0);
272
273 return result;
274}
275
276std::string Formatter::formatComment(const std::string& comment, const std::size_t indent) const
277{
278 std::string result = prefix(indent);
279 for (std::size_t i = 0, end = comment.size(); i < end; ++i)
280 {
281 result += comment[i];
282 if (comment[i] == '\n' && i != end - 1)
283 result += prefix(indent);
284 }
285
286 return result;
287}
288
289std::string Formatter::formatBlock(const Node& node, const std::size_t indent, const bool after_newline)
290{
291 if (node.constList().empty())
292 return "()";
293
294 const Node first = node.constList().front();
295 if (first.nodeType() == NodeType::Keyword)
296 {
297 switch (first.keyword())
298 {
299 case Keyword::Fun:
300 return formatFunction(node, indent);
301 case Keyword::Let:
302 [[fallthrough]];
303 case Keyword::Mut:
304 [[fallthrough]];
305 case Keyword::Set:
306 return formatVariable(node, indent);
307 case Keyword::If:
308 return formatCondition(node, indent);
309 case Keyword::While:
310 return formatLoop(node, indent);
311 case Keyword::Begin:
312 return formatBegin(node, indent, after_newline);
313 case Keyword::Import:
314 return formatImport(node, indent);
315 case Keyword::Del:
316 return formatDel(node, indent);
317 }
318 // HACK: should never reach, but the compiler insists that the function doesn't return in every code path
319 return "";
320 }
321 return formatCall(node, indent);
322}
323
324std::string Formatter::formatFunction(const Node& node, const std::size_t indent)
325{
326 const Node args_node = node.constList()[1];
327 const Node body_node = node.constList()[2];
328
329 std::string formatted_args;
330
331 if (!args_node.comment().empty())
332 {
333 formatted_args += "\n";
334 formatted_args += formatComment(args_node.comment(), indent + 1);
335 formatted_args += prefix(indent + 1);
336 }
337 else
338 formatted_args += " ";
339
340 if (args_node.isListLike())
341 {
342 bool comment_in_args = false;
343 std::string args;
344 const bool split = (isLongLine(args_node) || !args_node.comment().empty());
345
346 for (std::size_t i = 0, end = args_node.constList().size(); i < end; ++i)
347 {
348 const Node arg_i = args_node.constList()[i];
349 if (!arg_i.comment().empty())
350 comment_in_args = true;
351
352 args += format(arg_i, indent + ((comment_in_args || split) ? 1 : 0), i > 0 && (comment_in_args || split));
353 if (i != end - 1)
354 args += (comment_in_args || split) ? '\n' : ' ';
355 }
356
357 formatted_args += fmt::format("({}{})", (comment_in_args ? "\n" : ""), args);
358 }
359 else
360 formatted_args += format(args_node, indent, false);
361
362 const std::string same_line_f = format(body_node, indent + 1, false);
363 if (!shouldSplitOnNewline(body_node, isOnMultipleLines(same_line_f)) && args_node.comment().empty())
364 return fmt::format("(fun{} {})", formatted_args, same_line_f);
365 return fmt::format("(fun{}\n{})", formatted_args, format(body_node, indent + 1, true));
366}
367
368std::string Formatter::formatVariable(const Node& node, const std::size_t indent)
369{
370 const auto keyword = std::string(keywords[static_cast<std::size_t>(node.constList()[0].keyword())]);
371
372 const Node body_node = node.constList()[2];
373 const std::string formatted_bind = format(node.constList()[1], indent, false);
374
375 // we don't want to add another indentation level here, because it would result in a (let a (fun ()\n{indent+=4}...))
376 const std::string same_line_f = format(body_node, indent, false);
377 if (isFuncDef(body_node) || !shouldSplitOnNewline(body_node, isOnMultipleLines(same_line_f)))
378 return fmt::format("({} {} {})", keyword, formatted_bind, same_line_f);
379 return fmt::format("({} {}\n{})", keyword, formatted_bind, format(body_node, indent + 1, true));
380}
381
382std::string Formatter::formatCondition(const Node& node, const std::size_t indent, const bool is_macro)
383{
384 const Node cond_node = node.constList()[1];
385 const Node then_node = node.constList()[2];
386
387 bool cond_on_newline = false;
388 const std::string formatted_cond = format(cond_node, indent + 1, false);
389 if (formatted_cond.find('\n') != std::string::npos)
390 cond_on_newline = true;
391
392 std::string if_cond_formatted = fmt::format(
393 "({}if{}{}",
394 is_macro ? "$" : "",
395 cond_on_newline ? "\n" : " ",
396 cond_on_newline ? format(cond_node, indent + 1, true) : formatted_cond);
397
398 // (if cond then)
399 if (node.constList().size() == 3)
400 {
401 const std::string same_line_f = format(then_node, indent + 1, false);
402 const bool split_then_newline = shouldSplitOnNewline(then_node, isOnMultipleLines(same_line_f)) || isBeginBlock(then_node);
403 if (cond_on_newline || split_then_newline)
404 return fmt::format("{}\n{})", if_cond_formatted, format(then_node, indent + 1, true));
405 return fmt::format("{} {})", if_cond_formatted, same_line_f);
406 }
407 // (if cond then else)
408 return fmt::format(
409 "{}\n{}\n{}{})",
410 if_cond_formatted,
411 format(then_node, indent + 1, true),
412 format(node.constList()[3], indent + 1, true),
413 node.constList()[3].commentAfter().empty() ? "" : ("\n" + prefix(indent)));
414}
415
416std::string Formatter::formatLoop(const Node& node, const std::size_t indent)
417{
418 const Node cond_node = node.constList()[1];
419 const Node body_node = node.constList()[2];
420
421 const std::string formatted_cond = format(cond_node, indent + 1, false);
422 const bool cond_on_newline = isOnMultipleLines(formatted_cond);
423
424 if (cond_on_newline || shouldSplitOnNewline(body_node, cond_on_newline))
425 return fmt::format(
426 "(while{}{}\n{})",
427 cond_on_newline ? "\n" : " ",
428 cond_on_newline ? format(cond_node, indent + 1, true) : formatted_cond,
429 format(body_node, indent + 1, true));
430 return fmt::format(
431 "(while {} {})",
432 formatted_cond,
433 format(body_node, indent + 1, false));
434}
435
436std::string Formatter::formatBegin(const Node& node, const std::size_t indent, const bool after_newline)
437{
438 // only the keyword begin is present
439 if (node.constList().size() == 1)
440 return "{}";
441
442 // after a new line, we need to increment our indentation level
443 // if the block is a top level one, we also need to increment indentation level
444 const std::size_t inner_indentation = indent + (after_newline ? 1 : 0) + (indent == 0 ? 1 : 0);
445
446 std::string result = "{\n";
447 // skip begin keyword
448 for (std::size_t i = 1, end = node.constList().size(); i < end; ++i)
449 {
450 const Node child = node.constList()[i];
451 // we want to preserve the node grouping by the user, but remove useless duplicate new line
452 // but that shouldn't apply to the first node of the block
453 if (shouldAddNewLineBetweenNodes(node, i) && i > 1)
454 result += "\n";
455
456 result += format(child, inner_indentation, true);
457 if (i != end - 1)
458 result += "\n";
459 }
460
461 // if the last node has a comment, add a new line
462 if (!node.constList().empty() && !node.constList().back().commentAfter().empty())
463 result += "\n" + prefix(indent) + "}";
464 else
465 result += " }";
466 return result;
467}
468
469std::string Formatter::formatImport(const Node& node, const std::size_t indent)
470{
471 const Node package_node = node.constList()[1];
472 std::string package;
473
474 if (!package_node.comment().empty())
475 package += "\n" + formatComment(package_node.comment(), indent + 1) + prefix(indent + 1);
476 else
477 package += " ";
478
479 for (std::size_t i = 0, end = package_node.constList().size(); i < end; ++i)
480 {
481 package += format(package_node.constList()[i], indent + 1, false);
482 if (i != end - 1)
483 package += ".";
484 }
485
486 const Node symbols = node.constList()[2];
487 if (symbols.nodeType() == NodeType::Symbol && symbols.string() == "*")
488 package += ":*";
489 else // symbols is a list
490 {
491 if (const auto& sym_list = symbols.constList(); !sym_list.empty())
492 {
493 const bool comment_after_last = !sym_list.back().commentAfter().empty();
494
495 for (const auto& sym : sym_list)
496 {
497 if (sym.comment().empty())
498 {
499 if (comment_after_last)
500 package += "\n" + prefix(indent + 1) + ":" + sym.string();
501 else
502 package += " :" + sym.string();
503 }
504 else
505 package += "\n" + formatComment(sym.comment(), indent + 1) + prefix(indent + 1) + ":" + sym.string();
506 }
507
508 if (comment_after_last)
509 {
510 package += " " + formatComment(sym_list.back().commentAfter(), /* indent= */ 0);
511 package += "\n" + prefix(indent + 1);
512 }
513 }
514 }
515
516 return fmt::format("(import{})", package);
517}
518
519std::string Formatter::formatDel(const Node& node, const std::size_t indent)
520{
521 std::string formatted_sym = format(node.constList()[1], indent + 1, false);
522 if (formatted_sym.find('\n') != std::string::npos)
523 return fmt::format("(del\n{})", formatted_sym);
524 return fmt::format("(del {})", formatted_sym);
525}
526
527std::string Formatter::formatCall(const Node& node, const std::size_t indent)
528{
529 const CallKind kind = callKind(node);
530 bool is_multiline = false;
531
532 std::vector<std::string> formatted_args;
533 for (std::size_t i = 1, end = node.constList().size(); i < end; ++i)
534 {
535 formatted_args.push_back(format(node.constList()[i], indent, false));
536 // if we have at least one argument taking multiple lines, split them all on their own line
537 if (formatted_args.back().find('\n') != std::string::npos || !node.constList()[i].commentAfter().empty())
538 is_multiline = true;
539 }
540
541 std::string result = kind == CallKind::List ? "[" : "(" + format(node.constList()[0], indent, false);
542
543 // Split args on multiple lines even if, individually, they fit in the configured line length, if grouped together
544 // on a single line they are too long
545 const std::size_t args_line_length = std::accumulate(
546 formatted_args.begin(),
547 formatted_args.end(),
548 result.size() + 1, // +1 to count the closing paren/bracket
549 [](const std::size_t acc, const std::string& val) {
550 return acc + val.size() + 1_z;
551 });
552 if (args_line_length >= FormatterConfig::LongLineLength)
553 is_multiline = true;
554
555 for (std::size_t i = 0, end = formatted_args.size(); i < end; ++i)
556 {
557 const std::string& formatted_node = formatted_args[i];
558 if (kind == CallKind::Dict)
559 {
560 if (i % 2 == 0 && formatted_args.size() > 2) // one pair per line if we have at least 2 key-value pairs
561 result += "\n" + format(node.constList()[i + 1], indent + 1, true);
562 else
563 result += " " + formatted_node;
564 }
565 else if (kind == CallKind::Switch)
566 {
567 // % 1 because we want `(switch var` to stay together, then the pairs on their own lines
568 if (i % 2 == 1 && formatted_args.size() > 3) // one pair per line, same as dict
569 result += "\n" + format(node.constList()[i + 1], indent + 1, true);
570 else
571 result += " " + formatted_node;
572 }
573 else if (is_multiline)
574 result += "\n" + format(node.constList()[i + 1], indent + 1, true);
575 else if (kind == CallKind::List && i == 0)
576 result += formatted_node;
577 else // put all arguments on the same line
578 result += " " + formatted_node;
579 }
580 if (!node.constList().back().commentAfter().empty())
581 result += "\n" + prefix(indent);
582
583 result += kind == CallKind::List ? "]" : ")";
584 return result;
585}
586
587std::string Formatter::formatMacro(const Node& node, const std::size_t indent)
588{
589 if (isListStartingWithKeyword(node, Keyword::If))
590 return formatCondition(node, indent, /* is_macro= */ true);
591
592 std::string result = "(macro ";
593 bool after_newline = false;
594
595 for (std::size_t i = 0, end = node.constList().size(); i < end; ++i)
596 {
597 result += format(node.constList()[i], indent + 1, after_newline);
598 after_newline = false;
599
600 if (!node.constList()[i].commentAfter().empty())
601 {
602 result += "\n";
603 after_newline = true;
604 }
605 else if (i != end - 1)
606 result += " ";
607 }
608
609 return result + ")";
610}
Common code for the compiler.
Constants used by ArkScript.
#define ARK_NO_NAME_FILE
Definition Constants.hpp:34
Tools to report code errors nicely to the user.
ArkScript homemade exceptions.
Lots of utilities about the filesystem.
CallKind
Definition Formatter.hpp:16
User defined literals for Ark internals.
void warn(const char *fmt, Args &&... args)
Write a warn level log using fmtlib.
Definition Logger.hpp:80
A node of an Abstract Syntax Tree for ArkScript.
Definition Node.hpp:32
NodeType nodeType() const noexcept
Return the node type.
Definition Node.cpp:78
bool isListLike() const noexcept
Check if the node is a list like node.
Definition Node.cpp:83
const std::string & string() const noexcept
Return the string held by the value (if the node type allows it)
Definition Node.cpp:38
const std::vector< Node > & constList() const noexcept
Return the list of sub-nodes held by the node.
Definition Node.cpp:73
Keyword keyword() const noexcept
Return the keyword held by the value (if the node type allows it)
Definition Node.cpp:48
bool isRawString() const noexcept
Check if a node is a raw string.
Definition Node.cpp:164
const std::string & comment() const noexcept
Return the comment attached to this node, if any.
Definition Node.cpp:179
FileSpan position() const noexcept
Get the span of the node (start and end)
Definition Node.cpp:169
const std::string & commentAfter() const noexcept
Return the comment attached after this node, if any.
Definition Node.cpp:184
double number() const noexcept
Return the number held by the value (if the node type allows it)
Definition Node.cpp:43
void process(const std::string &filename, const std::string &code)
Parse the given code.
Definition Parser.cpp:51
const Node & ast() const noexcept
Definition Parser.cpp:92
std::string formatMacro(const Ark::internal::Node &node, std::size_t indent)
Ark::internal::Logger m_logger
Definition Formatter.hpp:54
void run()
Read the file and process it. The file isn't modified.
Definition Formatter.cpp:24
std::string formatVariable(const Ark::internal::Node &node, std::size_t indent)
bool codeModified() const
Definition Formatter.cpp:62
std::string formatBlock(const Ark::internal::Node &node, std::size_t indent, bool after_newline)
std::string formatDel(const Ark::internal::Node &node, std::size_t indent)
void processAst(const Ark::internal::Node &ast)
Definition Formatter.cpp:67
static std::string prefix(const std::size_t indent)
Compute indentation level.
std::string formatCall(const Ark::internal::Node &node, std::size_t indent)
static bool isBeginBlock(const Ark::internal::Node &node)
Check if a node is a begin block.
bool m_dry_run
If true, only prints the formatted file instead of saving it to disk.
Definition Formatter.hpp:50
static bool isFuncCall(const Ark::internal::Node &node)
Check if a node is a function call (foo bar egg)
static bool isFuncDef(const Ark::internal::Node &node)
Check if a node is a function definition (fun (args) body)
Ark::internal::Parser m_parser
Definition Formatter.hpp:51
bool shouldAddNewLineBetweenNodes(const Ark::internal::Node &node, std::size_t at)
Decide if we should add a newline after a node in a block.
std::string formatBegin(const Ark::internal::Node &node, std::size_t indent, bool after_newline)
bool m_updated
True if the original code now difer from the formatted one.
Definition Formatter.hpp:53
static CallKind callKind(const Ark::internal::Node &node)
static bool isOnMultipleLines(const std::string &formatted)
Formatter(bool dry_run)
Definition Formatter.cpp:16
std::string formatFunction(const Ark::internal::Node &node, std::size_t indent)
std::string formatLoop(const Ark::internal::Node &node, std::size_t indent)
bool shouldSplitOnNewline(const Ark::internal::Node &node, bool on_multiple_lines)
Decide if a node should be split on a newline or not.
std::string m_output
Definition Formatter.hpp:52
const std::string & output() const
Definition Formatter.cpp:57
std::string formatImport(const Ark::internal::Node &node, std::size_t indent)
std::string formatComment(const std::string &comment, std::size_t indent) const
const std::string m_filename
Definition Formatter.hpp:49
void runWithString(const std::string &code)
Definition Formatter.cpp:41
void warnIfCommentsWereRemoved(const std::string &original_code, const std::string &filename)
Given the original code, produce a warning if comments from it were removed during formatting.
Definition Formatter.cpp:90
static std::size_t lineOfLastNodeIn(const Ark::internal::Node &node)
Compute the line on which the deepest right most node of node is at.
std::string format(const Ark::internal::Node &node, std::size_t indent, bool after_newline)
Handles all node formatting.
bool isLongLine(const Ark::internal::Node &node)
static bool isListStartingWithKeyword(const Ark::internal::Node &node, Ark::internal::Keyword keyword)
Check if a given node starts with a given keyword.
std::string formatCondition(const Ark::internal::Node &node, std::size_t indent, bool is_macro=false)
ARK_API void generate(const CodeError &e, std::ostream &os=std::cerr, bool colorize=true)
Generate a diagnostic from an error and print it to the standard error output.
std::string readFile(const std::string &name)
Helper to read a file.
Definition Files.hpp:47
std::vector< std::string > splitString(const std::string &source, const char sep)
Cut a string into pieces, given a character separator.
Definition Utils.hpp:31
@ Raw
Keep all text as is without modifying it (useful for the code formatter)
Keyword
The different keywords available.
Definition Common.hpp:79
constexpr std::array< std::string_view, 9 > keywords
List of available keywords in ArkScript.
Definition Common.hpp:92
STL namespace.
CodeError thrown by the compiler (parser, macro processor, optimizer, and compiler itself)
std::size_t line
0-indexed line number
Definition Position.hpp:22
static constexpr std::size_t LongLineLength
Max number of characters per line segment to consider splitting.
Definition Formatter.hpp:12