ArkScript
A small, lisp-inspired, functional scripting language
Parser.cpp
Go to the documentation of this file.
2
3#include <fmt/core.h>
4
5namespace Ark::internal
6{
7 Parser::Parser(const unsigned debug, const bool interpret) :
8 BaseParser(), m_interpret(interpret), m_logger("Parser", debug),
9 m_ast(NodeType::List), m_imports({}), m_allow_macro_behavior(0),
10 m_nested_nodes(0)
11 {
12 m_ast.push_back(Node(Keyword::Begin));
13
14 m_parsers = {
15 [this]() {
16 return wrapped(&Parser::letMutSet, "variable assignment or declaration");
17 },
18 [this]() {
19 return wrapped(&Parser::function, "function");
20 },
21 [this]() {
22 return wrapped(&Parser::condition, "condition");
23 },
24 [this]() {
25 return wrapped(&Parser::loop, "loop");
26 },
27 [this]() {
28 return import_();
29 },
30 [this]() {
31 return block();
32 },
33 [this]() {
34 return wrapped(&Parser::macroCondition, "$if");
35 },
36 [this]() {
37 return macro();
38 },
39 [this]() {
40 return wrapped(&Parser::del, "del");
41 },
42 [this]() {
43 return functionCall();
44 },
45 [this]() {
46 return list();
47 }
48 };
49 }
50
51 void Parser::process(const std::string& filename, const std::string& code)
52 {
53 m_logger.traceStart("process");
54 initParser(filename, code);
55
56 while (!isEOF())
57 {
58 std::string comment;
60 if (isEOF())
61 {
62 if (!comment.empty())
63 m_ast.list().back().attachCommentAfter(comment);
64 break;
65 }
66
67 const auto pos = getCount();
68 if (auto n = node())
69 {
70 m_ast.push_back(n->attachNearestCommentBefore(n->comment() + comment));
71 comment.clear();
73 m_ast.list().back().attachCommentAfter(comment);
74 }
75 else
76 {
77 backtrack(pos);
78 std::string out = peek();
79 std::string message;
80 if (out == ")")
81 message = "Unexpected closing paren";
82 else if (out == "}")
83 message = "Unexpected closing bracket";
84 else if (out == "]")
85 message = "Unexpected closing square bracket";
86 else
87 errorWithNextToken("invalid syntax, expected node");
88 errorWithNextToken(message);
89 }
90 }
91
93 }
94
95 const Node& Parser::ast() const noexcept
96 {
97 return m_ast;
98 }
99
100 const std::vector<Import>& Parser::imports() const
101 {
102 return m_imports;
103 }
104
105 Node& Parser::setNodePosAndFilename(Node& node, const std::optional<FilePosition>& cursor) const
106 {
107 if (node.line() != 0 || node.col() != 0)
108 return node;
109
110 const auto [row, col] = cursor.value_or(getCursor());
111 node.setPos(row, col);
112 node.setFilename(m_filename);
113 return node;
114 }
115
116 std::optional<Node> Parser::node()
117 {
119
121 errorWithNextToken(fmt::format("Too many nested node while parsing, exceeds limit of {}. Consider rewriting your code by breaking it in functions and macros.", MaxNestedNodes));
122
123 // save current position in buffer to be able to go back if needed
124 const auto position = getCount();
125 std::optional<Node> result = std::nullopt;
126
127 for (auto&& parser : m_parsers)
128 {
129 result = parser();
130
131 if (result)
132 break;
133 backtrack(position);
134 }
135
136 // return std::nullopt only on parsing error, nothing matched, the user provided terrible code
138 return result;
139 }
140
141 std::optional<Node> Parser::letMutSet()
142 {
143 std::optional<Node> leaf { NodeType::List };
144 setNodePosAndFilename(leaf.value());
145
146 std::string token;
147 if (!oneOf({ "let", "mut", "set" }, &token))
148 return std::nullopt;
149 std::string comment;
151 leaf->attachNearestCommentBefore(comment);
152
153 if (token == "let")
154 leaf->push_back(Node(Keyword::Let));
155 else if (token == "mut")
156 leaf->push_back(Node(Keyword::Mut));
157 else // "set"
158 leaf->push_back(Node(Keyword::Set));
159
161 {
162 const auto position = getCount();
163 if (const auto value = nodeOrValue(); value.has_value())
164 {
165 const auto sym = value.value();
166 if (sym.nodeType() == NodeType::List || sym.nodeType() == NodeType::Symbol || sym.nodeType() == NodeType::Macro || sym.nodeType() == NodeType::Spread)
167 leaf->push_back(sym);
168 else
169 error(fmt::format("Can not use a {} as a symbol name, even in a macro", nodeTypes[static_cast<std::size_t>(sym.nodeType())]), sym.repr());
170 }
171 else
172 backtrack(position);
173 }
174
175 if (leaf->constList().size() == 1)
176 {
177 // we haven't parsed anything while in "macro state"
178 std::string symbol_name;
179 if (!name(&symbol_name))
180 errorWithNextToken(token + " needs a symbol");
181
182 leaf->push_back(Node(NodeType::Symbol, symbol_name));
183 }
184
185 comment.clear();
187
188 if (auto value = nodeOrValue(); value.has_value())
189 leaf->push_back(value.value().attachNearestCommentBefore(comment));
190 else
191 errorWithNextToken("Expected a value");
192
193 return leaf;
194 }
195
196 std::optional<Node> Parser::del()
197 {
198 std::optional<Node> leaf { NodeType::List };
199 setNodePosAndFilename(leaf.value());
200
201 if (!oneOf({ "del" }))
202 return std::nullopt;
203 leaf->push_back(Node(Keyword::Del));
204
205 std::string comment;
207
208 std::string symbol_name;
209 if (!name(&symbol_name))
210 errorWithNextToken("del needs a symbol");
211
212 leaf->push_back(Node(NodeType::Symbol, symbol_name));
213 leaf->list().back().attachNearestCommentBefore(comment);
214 setNodePosAndFilename(leaf->list().back());
215
216 return leaf;
217 }
218
219 std::optional<Node> Parser::condition()
220 {
221 std::optional<Node> leaf { NodeType::List };
222 setNodePosAndFilename(leaf.value());
223
224 if (!oneOf({ "if" }))
225 return std::nullopt;
226
227 std::string comment;
229
230 leaf->push_back(Node(Keyword::If));
231
232 if (auto cond_expr = nodeOrValue(); cond_expr.has_value())
233 leaf->push_back(cond_expr.value().attachNearestCommentBefore(comment));
234 else
235 errorWithNextToken("`if' needs a valid condition");
236
237 comment.clear();
239
240 if (auto value_if_true = nodeOrValue(); value_if_true.has_value())
241 leaf->push_back(value_if_true.value().attachNearestCommentBefore(comment));
242 else
243 errorWithNextToken("Expected a node or value after condition");
244
245 comment.clear();
247
248 if (auto value_if_false = nodeOrValue(); value_if_false.has_value())
249 {
250 leaf->push_back(value_if_false.value().attachNearestCommentBefore(comment));
251 comment.clear();
253 leaf->list().back().attachCommentAfter(comment);
254 }
255 else if (!comment.empty())
256 leaf->attachCommentAfter(comment);
257
258 setNodePosAndFilename(leaf->list().back());
259 return leaf;
260 }
261
262 std::optional<Node> Parser::loop()
263 {
264 std::optional<Node> leaf { NodeType::List };
265 setNodePosAndFilename(leaf.value());
266
267 if (!oneOf({ "while" }))
268 return std::nullopt;
269
270 std::string comment;
272
273 leaf->push_back(Node(Keyword::While));
274
275 if (auto cond_expr = nodeOrValue(); cond_expr.has_value())
276 leaf->push_back(cond_expr.value().attachNearestCommentBefore(comment));
277 else
278 errorWithNextToken("`while' needs a valid condition");
279
280 comment.clear();
282
283 if (auto body = nodeOrValue(); body.has_value())
284 leaf->push_back(body.value().attachNearestCommentBefore(comment));
285 else
286 errorWithNextToken("Expected a node or value after loop condition");
287
288 setNodePosAndFilename(leaf->list().back());
289 return leaf;
290 }
291
292 std::optional<Node> Parser::import_()
293 {
294 std::optional<Node> leaf { NodeType::List };
295 setNodePosAndFilename(leaf.value());
296
297 const auto [row, col] = getCursor();
298 auto context = generateErrorContext("(");
299 if (!accept(IsChar('(')))
300 return std::nullopt;
301
302 std::string comment;
304 leaf->attachNearestCommentBefore(comment);
305
306 if (!oneOf({ "import" }))
307 return std::nullopt;
308 comment.clear();
310 leaf->push_back(Node(Keyword::Import));
311
312 Import import_data;
313 import_data.col = col;
314 import_data.line = row;
315
316 const auto pos = getCount();
317 if (!packageName(&import_data.prefix))
318 errorWithNextToken("Import expected a package name");
319
320 if (import_data.prefix.size() > 255)
321 {
322 backtrack(pos);
323 errorWithNextToken(fmt::format("Import name too long, expected at most 255 characters, got {}", import_data.prefix.size()));
324 }
325 import_data.package.push_back(import_data.prefix);
326
327 Node packageNode(NodeType::List);
329 packageNode.push_back(Node(NodeType::Symbol, import_data.prefix));
330
331 // first, parse the package name
332 while (!isEOF())
333 {
334 // parsing package folder.foo.bar.yes
335 if (accept(IsChar('.')))
336 {
337 std::string path;
338 if (!packageName(&path))
339 errorWithNextToken("Package name expected after '.'");
340 else
341 {
342 packageNode.push_back(Node(NodeType::Symbol, path));
343 setNodePosAndFilename(packageNode.list().back());
344 import_data.package.push_back(path);
345 import_data.prefix = path; // in the end we will store the last element of the package, which is what we want
346
347 if (path.size() > 255)
348 {
349 backtrack(pos);
350 errorWithNextToken(fmt::format("Import name too long, expected at most 255 characters, got {}", path.size()));
351 }
352 }
353 }
354 else if (accept(IsChar(':')) && accept(IsChar('*'))) // parsing :*
355 {
356 leaf->push_back(packageNode);
357 leaf->push_back(Node(NodeType::Symbol, "*"));
358 setNodePosAndFilename(leaf->list().back());
359
360 space();
361 expectSuffixOrError(')', fmt::format("in import `{}'", import_data.toPackageString()), context);
362
363 // save the import data structure to know we encounter an import node, and retrieve its data more easily later on
364 import_data.with_prefix = false;
365 import_data.is_glob = true;
366 m_imports.push_back(import_data);
367
368 return leaf;
369 }
370 else
371 break;
372 }
373
374 Node symbols(NodeType::List);
375 setNodePosAndFilename(symbols);
376 // then parse the symbols to import, if any
377 if (space())
378 {
379 comment.clear();
381
382 while (!isEOF())
383 {
384 if (accept(IsChar(':'))) // parsing potential :a :b :c
385 {
386 std::string symbol_name;
387 if (!name(&symbol_name))
388 errorWithNextToken("Expected a valid symbol to import");
389 if (symbol_name == "*")
390 error(fmt::format("Glob patterns can not be separated from the package, use (import {}:*) instead", import_data.toPackageString()), symbol_name);
391
392 if (symbol_name.size() >= 2 && symbol_name[symbol_name.size() - 2] == ':' && symbol_name.back() == '*')
393 {
394 backtrack(getCount() - 2); // we can backtrack n-2 safely here because we know the previous chars were ":*"
395 error("Glob pattern can not follow a symbol to import", ":*");
396 }
397
398 symbols.push_back(Node(NodeType::Symbol, symbol_name).attachNearestCommentBefore(comment));
399 comment.clear();
400 setNodePosAndFilename(symbols.list().back());
401 import_data.symbols.push_back(symbol_name);
402 // we do not need the prefix when importing specific symbols
403 import_data.with_prefix = false;
404 }
405
406 if (!space())
407 break;
408 comment.clear();
410 }
411
412 if (!comment.empty() && !symbols.list().empty())
413 symbols.list().back().attachCommentAfter(comment);
414 }
415
416 leaf->push_back(packageNode);
417 leaf->push_back(symbols);
418 // save the import data
419 m_imports.push_back(import_data);
420
421 comment.clear();
423 leaf->list().back().attachCommentAfter(comment);
424
425 expectSuffixOrError(')', fmt::format("in import `{}'", import_data.toPackageString()), context);
426 return leaf;
427 }
428
429 std::optional<Node> Parser::block()
430 {
431 std::optional<Node> leaf { NodeType::List };
432 setNodePosAndFilename(leaf.value());
433
434 auto context = generateErrorContext("(");
435 bool alt_syntax = false;
436 std::string comment;
437 if (accept(IsChar('(')))
438 {
440 if (!oneOf({ "begin" }))
441 return std::nullopt;
442 }
443 else if (accept(IsChar('{')))
444 alt_syntax = true;
445 else
446 return std::nullopt;
447
448 leaf->setAltSyntax(alt_syntax);
449 leaf->push_back(Node(Keyword::Begin).attachNearestCommentBefore(comment));
450
451 comment.clear();
453
454 while (!isEOF())
455 {
456 if (auto value = nodeOrValue(); value.has_value())
457 {
458 leaf->push_back(value.value().attachNearestCommentBefore(comment));
459 comment.clear();
461 }
462 else
463 break;
464 }
465
467 expectSuffixOrError(alt_syntax ? '}' : ')', "to close block", context);
468 setNodePosAndFilename(leaf->list().back());
469 leaf->list().back().attachCommentAfter(comment);
470 return leaf;
471 }
472
473 std::optional<Node> Parser::functionArgs()
474 {
475 expect(IsChar('('));
476 std::optional<Node> args { NodeType::List };
477 setNodePosAndFilename(args.value());
478
479 std::string comment;
481 args->attachNearestCommentBefore(comment);
482
483 bool has_captures = false;
484
485 while (!isEOF())
486 {
487 const auto pos = getCursor();
488 if (accept(IsChar('&'))) // captures
489 {
490 has_captures = true;
491 std::string capture;
492 if (!name(&capture))
493 break;
495 setNodePosAndFilename(capture_node, pos);
496 args->push_back(capture_node);
497 }
498 else
499 {
500 const auto count = getCount();
501 std::string symbol_name;
502 if (!name(&symbol_name))
503 break;
504 if (has_captures)
505 {
506 backtrack(count);
507 error("Captured variables should be at the end of the argument list", symbol_name);
508 }
509
511 setNodePosAndFilename(arg_node, pos);
512 args->push_back(arg_node);
513 }
514
515 comment.clear();
517 }
518
519 if (accept(IsChar(')')))
520 return args;
521 return std::nullopt;
522 }
523
524 std::optional<Node> Parser::function()
525 {
526 std::optional<Node> leaf { NodeType::List };
527 setNodePosAndFilename(leaf.value());
528
529 if (!oneOf({ "fun" }))
530 return std::nullopt;
531 leaf->push_back(Node(Keyword::Fun));
532
533 std::string comment_before_args;
534 newlineOrComment(&comment_before_args);
535
536 while (m_allow_macro_behavior > 0)
537 {
538 const auto position = getCount();
539
540 // args
541 if (const auto value = nodeOrValue(); value.has_value())
542 {
543 // if value is nil, just add an empty argument bloc to prevent bugs when
544 // declaring functions inside macros
545 Node args = value.value();
547 if (args.nodeType() == NodeType::Symbol && args.string() == "nil")
548 leaf->push_back(Node(NodeType::List));
549 else
550 leaf->push_back(args);
551 }
552 else
553 {
554 backtrack(position);
555 break;
556 }
557
558 std::string comment;
560 // body
561 if (auto value = nodeOrValue(); value.has_value())
562 leaf->push_back(value.value().attachNearestCommentBefore(comment));
563 else
564 errorWithNextToken("Expected a body for the function");
565 setNodePosAndFilename(leaf->list().back());
566 return leaf;
567 }
568
569 const auto position = getCount();
570 if (auto args = functionArgs(); args.has_value())
571 leaf->push_back(args.value().attachNearestCommentBefore(comment_before_args));
572 else
573 {
574 backtrack(position);
575
576 if (auto value = nodeOrValue(); value.has_value())
577 leaf->push_back(value.value().attachNearestCommentBefore(comment_before_args));
578 else
579 errorWithNextToken("Expected an argument list");
580 }
581
582 std::string comment;
584
585 if (auto value = nodeOrValue(); value.has_value())
586 leaf->push_back(value.value().attachNearestCommentBefore(comment));
587 else
588 errorWithNextToken("Expected a body for the function");
589
590 setNodePosAndFilename(leaf->list().back());
591 return leaf;
592 }
593
594 std::optional<Node> Parser::macroCondition()
595 {
596 std::optional<Node> leaf { NodeType::Macro };
597 setNodePosAndFilename(leaf.value());
598
599 if (!oneOf({ "$if" }))
600 return std::nullopt;
601 leaf->push_back(Node(Keyword::If));
602
603 std::string comment;
605 leaf->attachNearestCommentBefore(comment);
606
607 if (const auto cond_expr = nodeOrValue(); cond_expr.has_value())
608 leaf->push_back(cond_expr.value());
609 else
610 errorWithNextToken("$if need a valid condition");
611
612 comment.clear();
614
615 if (auto value_if_true = nodeOrValue(); value_if_true.has_value())
616 leaf->push_back(value_if_true.value().attachNearestCommentBefore(comment));
617 else
618 errorWithNextToken("Expected a node or value after condition");
619
620 comment.clear();
622
623 if (auto value_if_false = nodeOrValue(); value_if_false.has_value())
624 {
625 leaf->push_back(value_if_false.value().attachNearestCommentBefore(comment));
626 comment.clear();
628 leaf->list().back().attachCommentAfter(comment);
629 }
630
631 setNodePosAndFilename(leaf->list().back());
632 return leaf;
633 }
634
635 std::optional<Node> Parser::macroArgs()
636 {
637 if (!accept(IsChar('(')))
638 return std::nullopt;
639
640 std::optional<Node> args { NodeType::List };
641 setNodePosAndFilename(args.value());
642
643 std::string comment;
645 args->attachNearestCommentBefore(comment);
646
647 std::vector<std::string> names;
648 while (!isEOF())
649 {
650 const auto pos = getCount();
651
652 std::string arg_name;
653 if (!name(&arg_name))
654 break;
655 comment.clear();
657 args->push_back(Node(NodeType::Symbol, arg_name).attachNearestCommentBefore(comment));
658
659 if (std::ranges::find(names, arg_name) != names.end())
660 {
661 backtrack(pos);
662 errorWithNextToken(fmt::format("Argument names must be unique, can not reuse `{}'", arg_name));
663 }
664 names.push_back(arg_name);
665 }
666
667 const auto pos = getCount();
668 if (sequence("..."))
669 {
670 std::string spread_name;
671 if (!name(&spread_name))
672 errorWithNextToken("Expected a name for the variadic arguments list");
673 args->push_back(Node(NodeType::Spread, spread_name));
674
675 comment.clear();
677 args->list().back().attachCommentAfter(comment);
678
679 if (std::ranges::find(names, spread_name) != names.end())
680 {
681 backtrack(pos);
682 errorWithNextToken(fmt::format("Argument names must be unique, can not reuse `{}'", spread_name));
683 }
684 }
685
686 if (!accept(IsChar(')')))
687 return std::nullopt;
688 comment.clear();
690 {
691 if (args->list().empty())
692 args->attachCommentAfter(comment);
693 else
694 args->list().back().attachCommentAfter(comment);
695 }
696
697 return args;
698 }
699
700 std::optional<Node> Parser::macro()
701 {
702 std::optional<Node> leaf { NodeType::Macro };
703 setNodePosAndFilename(leaf.value());
704
705 auto context = generateErrorContext("(");
706 if (!accept(IsChar('(')))
707 return std::nullopt;
708 std::string comment;
710
711 if (!oneOf({ "macro" }))
712 return std::nullopt;
714 leaf->attachNearestCommentBefore(comment);
715
716 std::string symbol_name;
717 if (!name(&symbol_name))
718 errorWithNextToken("Expected a symbol to declare a macro");
719 comment.clear();
721
722 leaf->push_back(Node(NodeType::Symbol, symbol_name).attachNearestCommentBefore(comment));
723
724 const auto position = getCount();
725 if (const auto args = macroArgs(); args.has_value())
726 leaf->push_back(args.value());
727 else
728 {
729 // if we couldn't parse arguments, then we have a value
730 backtrack(position);
731
733 const auto value = nodeOrValue();
735
736 if (value.has_value())
737 leaf->push_back(value.value());
738 else
739 errorWithNextToken(fmt::format("Expected an argument list, atom or node while defining macro `{}'", symbol_name));
740
741 setNodePosAndFilename(leaf->list().back());
742 comment.clear();
744 leaf->list().back().attachCommentAfter(comment);
745 expectSuffixOrError(')', fmt::format("to close macro `{}'", symbol_name), context);
746 return leaf;
747 }
748
750 const auto value = nodeOrValue();
752
753 if (value.has_value())
754 leaf->push_back(value.value());
755 else if (leaf->list().size() == 2) // the argument list is actually a function call and it's okay
756 {
757 setNodePosAndFilename(leaf->list().back());
758 comment.clear();
760 leaf->list().back().attachCommentAfter(comment);
761
762 expectSuffixOrError(')', fmt::format("to close macro `{}'", symbol_name), context);
763 return leaf;
764 }
765 else
766 {
767 backtrack(position);
768 errorWithNextToken(fmt::format("Expected a value while defining macro `{}'", symbol_name), context);
769 }
770
771 setNodePosAndFilename(leaf->list().back());
772 comment.clear();
774 leaf->list().back().attachCommentAfter(comment);
775
776 expectSuffixOrError(')', fmt::format("to close macro `{}'", symbol_name), context);
777 return leaf;
778 }
779
780 std::optional<Node> Parser::functionCall()
781 {
782 auto context = generateErrorContext("(");
783 if (!accept(IsChar('(')))
784 return std::nullopt;
785 std::string comment;
787 auto cursor = getCursor();
788
789 std::optional<Node> func;
790 if (auto sym_or_field = anyAtomOf({ NodeType::Symbol, NodeType::Field }); sym_or_field.has_value())
791 func = sym_or_field->attachNearestCommentBefore(comment);
792 else if (auto nested = node(); nested.has_value())
793 func = nested->attachNearestCommentBefore(comment);
794 else
795 return std::nullopt;
796 comment.clear();
798
799 std::optional<Node> leaf { NodeType::List };
800 setNodePosAndFilename(leaf.value(), cursor);
801 setNodePosAndFilename(func.value(), cursor);
802 leaf->push_back(func.value());
803
804 while (!isEOF())
805 {
806 if (auto arg = nodeOrValue(); arg.has_value())
807 {
808 leaf->push_back(arg.value().attachNearestCommentBefore(comment));
809 comment.clear();
811 }
812 else
813 break;
814 }
815
816 leaf->list().back().attachCommentAfter(comment);
817
818 comment.clear();
820 leaf->list().back().attachCommentAfter(comment);
821
822 expectSuffixOrError(')', fmt::format("in function call to `{}'", func.value().repr()), context);
823 return leaf;
824 }
825
826 std::optional<Node> Parser::list()
827 {
828 std::optional<Node> leaf { NodeType::List };
829 setNodePosAndFilename(leaf.value());
830
831 auto context = generateErrorContext("[");
832 if (!accept(IsChar('[')))
833 return std::nullopt;
834 leaf->setAltSyntax(true);
835 leaf->push_back(Node(NodeType::Symbol, "list"));
836
837 std::string comment;
839 leaf->attachNearestCommentBefore(comment);
840
841 comment.clear();
842 while (!isEOF())
843 {
844 if (auto value = nodeOrValue(); value.has_value())
845 {
846 leaf->push_back(value.value().attachNearestCommentBefore(comment));
847 comment.clear();
849 }
850 else
851 break;
852 }
853 leaf->list().back().attachCommentAfter(comment);
854
855 expectSuffixOrError(']', "to end list definition", context);
856 return leaf;
857 }
858
859 std::optional<Node> Parser::atom()
860 {
861 const auto pos = getCount();
862
863 if (auto res = Parser::number(); res.has_value())
864 return res;
865 backtrack(pos);
866
867 if (auto res = Parser::string(); res.has_value())
868 return res;
869 backtrack(pos);
870
871 if (auto res = Parser::spread(); m_allow_macro_behavior > 0 && res.has_value())
872 return res;
873 backtrack(pos);
874
875 if (auto res = Parser::field(); res.has_value())
876 return res;
877 backtrack(pos);
878
879 if (auto res = Parser::symbol(); res.has_value())
880 return res;
881 backtrack(pos);
882
883 if (auto res = Parser::nil(); res.has_value())
884 return res;
885 backtrack(pos);
886
887 return std::nullopt;
888 }
889
890 std::optional<Node> Parser::anyAtomOf(const std::initializer_list<NodeType> types)
891 {
892 auto cursor = getCursor();
893 if (auto value = atom(); value.has_value())
894 {
895 setNodePosAndFilename(value.value(), cursor);
896 for (const auto type : types)
897 {
898 if (value->nodeType() == type)
899 return value;
900 }
901 }
902 return std::nullopt;
903 }
904
905 std::optional<Node> Parser::nodeOrValue()
906 {
907 auto cursor = getCursor();
908 if (auto value = atom(); value.has_value())
909 {
910 setNodePosAndFilename(value.value(), cursor);
911 return value;
912 }
913 if (auto sub_node = node(); sub_node.has_value())
914 {
915 setNodePosAndFilename(sub_node.value(), cursor);
916 return sub_node;
917 }
918
919 return std::nullopt;
920 }
921
922 std::optional<Node> Parser::wrapped(std::optional<Node> (Parser::*parser)(), const std::string& name)
923 {
924 auto cursor = getCursor();
925 auto context = generateErrorContext("(");
926 if (!prefix('('))
927 return std::nullopt;
928 std::string comment;
930
931 if (auto result = (this->*parser)(); result.has_value())
932 {
933 result->attachNearestCommentBefore(result->comment() + comment);
934 setNodePosAndFilename(result.value(), cursor);
935
936 comment.clear();
938 result.value().attachCommentAfter(comment);
939
940 if (result->isListLike())
941 setNodePosAndFilename(result->list().back());
942 expectSuffixOrError(')', "after " + name, context);
943
944 comment.clear();
945 if (spaceComment(&comment))
946 result.value().attachCommentAfter(comment);
947
948 return result;
949 }
950
951 return std::nullopt;
952 }
953}
Parse ArkScript code, but do not handle any import declarations.
bool sequence(const std::string &s)
bool spaceComment(std::string *s=nullptr)
void initParser(const std::string &filename, const std::string &code)
bool expect(const CharPred &t, std::string *s=nullptr)
heck if a Character Predicate was able to parse, call next() if matching ; throw a CodeError if it do...
bool accept(const CharPred &t, std::string *s=nullptr)
check if a Character Predicate was able to parse, call next() if matching
bool newlineOrComment(std::string *s=nullptr)
void backtrack(long n)
Backtrack to a given position (this is NOT an offset!)
CodeErrorContext generateErrorContext(const std::string &expr)
std::string peek() const
bool oneOf(std::initializer_list< std::string > words, std::string *s=nullptr)
Fetch a token and try to match one of the given words.
bool space(std::string *s=nullptr)
bool name(std::string *s=nullptr)
bool comment(std::string *s=nullptr)
bool packageName(std::string *s=nullptr)
void errorWithNextToken(const std::string &message, const std::optional< CodeErrorContext > &additional_context=std::nullopt)
Fetch the next token (space and paren delimited) to generate an error.
void error(const std::string &error, std::string exp, const std::optional< CodeErrorContext > &additional_context=std::nullopt)
void expectSuffixOrError(char suffix, const std::string &context, const std::optional< CodeErrorContext > &additional_context=std::nullopt)
Check for a closing char or generate an error.
FilePosition getCursor() const
void traceStart(std::string &&trace_name)
Definition Logger.hpp:90
A node of an Abstract Syntax Tree for ArkScript.
Definition Node.hpp:30
NodeType nodeType() const noexcept
Return the node type.
Definition Node.cpp:78
const std::string & string() const noexcept
Return the string held by the value (if the node type allows it)
Definition Node.cpp:38
void setPos(std::size_t line, std::size_t col) noexcept
Set the Position of the node in the text.
Definition Node.cpp:117
Node & attachNearestCommentBefore(const std::string &comment)
Set the comment field with the nearest comment before this node.
Definition Node.cpp:128
void push_back(const Node &node) noexcept
Every node has a list as well as a value so we can push_back on all node no matter their type.
Definition Node.cpp:63
std::vector< Node > & list() noexcept
Return the list of sub-nodes held by the node.
Definition Node.cpp:68
std::vector< std::function< std::optional< Node >()> > m_parsers
Definition Parser.hpp:66
std::optional< Node > functionArgs()
Definition Parser.cpp:473
std::optional< Node > macro()
Definition Parser.cpp:700
std::optional< Node > wrapped(std::optional< Node >(Parser::*parser)(), const std::string &name)
Try to parse using a given parser, prefixing and suffixing it with (...), handling comments around th...
Definition Parser.cpp:922
std::optional< Node > atom()
Try to parse an atom (number, string, spread, field, symbol, nil)
Definition Parser.cpp:859
void process(const std::string &filename, const std::string &code)
Parse the given code.
Definition Parser.cpp:51
std::optional< Node > loop()
Definition Parser.cpp:262
Parser(unsigned debug, bool interpret=true)
Constructs a new Parser object.
Definition Parser.cpp:7
std::optional< Node > nil()
Definition Parser.hpp:244
std::optional< Node > symbol()
Definition Parser.hpp:224
std::optional< Node > number()
Definition Parser.hpp:91
std::optional< Node > macroArgs()
Definition Parser.cpp:635
const Node & ast() const noexcept
Definition Parser.cpp:95
std::optional< Node > nodeOrValue()
Try to parse an atom first, if it fails try to parse a node.
Definition Parser.cpp:905
std::optional< Node > import_()
Definition Parser.cpp:292
const std::vector< Import > & imports() const
Definition Parser.cpp:100
std::optional< Node > string()
Definition Parser.hpp:107
std::optional< Node > macroCondition()
Definition Parser.cpp:594
std::optional< Node > spread()
Definition Parser.hpp:232
std::optional< Node > condition()
Definition Parser.cpp:219
Node & setNodePosAndFilename(Node &node, const std::optional< FilePosition > &cursor=std::nullopt) const
Update a node given a file position.
Definition Parser.cpp:105
std::optional< Node > node()
Definition Parser.cpp:116
std::optional< Node > functionCall()
Definition Parser.cpp:780
std::optional< Node > field()
Definition Parser.hpp:198
unsigned m_allow_macro_behavior
Toggled on when inside a macro definition, off afterward.
Definition Parser.hpp:64
std::optional< Node > letMutSet()
Definition Parser.cpp:141
std::optional< Node > anyAtomOf(std::initializer_list< NodeType > types)
Try to parse an atom, if any, match its type against the given list.
Definition Parser.cpp:890
std::vector< Import > m_imports
Definition Parser.hpp:63
std::optional< Node > function()
Definition Parser.cpp:524
std::optional< Node > list()
Definition Parser.cpp:826
std::size_t m_nested_nodes
Nested node counter.
Definition Parser.hpp:65
std::optional< Node > block()
Definition Parser.cpp:429
std::optional< Node > del()
Definition Parser.cpp:196
constexpr std::array< std::string_view, 11 > nodeTypes
Node types as string, in the same order as the enum NodeType.
Definition Common.hpp:59
NodeType
The different node types available.
Definition Common.hpp:44
constexpr std::size_t MaxNestedNodes
Maximum number of nodes that can be nested while parsing code.
Definition Constants.hpp:65
std::size_t line
Definition Import.hpp:14
std::vector< std::string > symbols
List of symbols to import, can be empty if none provided. (import package :a :b)
Definition Import.hpp:48
std::size_t col
Position in the source file.
Definition Import.hpp:14
std::string prefix
The filename without the extension.
Definition Import.hpp:23
bool is_glob
Import as glob (import package:*)
Definition Import.hpp:42
std::string toPackageString() const
Definition Import.hpp:54
std::vector< std::string > package
Package with all the segments.
Definition Import.hpp:31
bool with_prefix
Import with prefix (import package)
Definition Import.hpp:37