ArkScript
A small, fast, functional and scripting language for video games
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 auto context = generateErrorContext("(");
298 if (!accept(IsChar('(')))
299 return std::nullopt;
300
301 std::string comment;
303 leaf->attachNearestCommentBefore(comment);
304
305 if (!oneOf({ "import" }))
306 return std::nullopt;
307 comment.clear();
309 leaf->push_back(Node(Keyword::Import));
310
311 Import import_data;
312
313 const auto pos = getCount();
314 if (!packageName(&import_data.prefix))
315 errorWithNextToken("Import expected a package name");
316
317 if (import_data.prefix.size() > 255)
318 {
319 backtrack(pos);
320 errorWithNextToken(fmt::format("Import name too long, expected at most 255 characters, got {}", import_data.prefix.size()));
321 }
322 import_data.package.push_back(import_data.prefix);
323
324 const auto [row, col] = getCursor();
325 import_data.col = col;
326 import_data.line = row;
327
328 Node packageNode(NodeType::List);
330 packageNode.push_back(Node(NodeType::Symbol, import_data.prefix));
331
332 // first, parse the package name
333 while (!isEOF())
334 {
335 // parsing package folder.foo.bar.yes
336 if (accept(IsChar('.')))
337 {
338 std::string path;
339 if (!packageName(&path))
340 errorWithNextToken("Package name expected after '.'");
341 else
342 {
343 packageNode.push_back(Node(NodeType::Symbol, path));
344 setNodePosAndFilename(packageNode.list().back());
345 import_data.package.push_back(path);
346 import_data.prefix = path; // in the end we will store the last element of the package, which is what we want
347
348 if (path.size() > 255)
349 {
350 backtrack(pos);
351 errorWithNextToken(fmt::format("Import name too long, expected at most 255 characters, got {}", path.size()));
352 }
353 }
354 }
355 else if (accept(IsChar(':')) && accept(IsChar('*'))) // parsing :*
356 {
357 leaf->push_back(packageNode);
358 leaf->push_back(Node(NodeType::Symbol, "*"));
359 setNodePosAndFilename(leaf->list().back());
360
361 space();
362 expectSuffixOrError(')', fmt::format("in import `{}'", import_data.toPackageString()), context);
363
364 // save the import data structure to know we encounter an import node, and retrieve its data more easily later on
365 import_data.with_prefix = false;
366 import_data.is_glob = true;
367 m_imports.push_back(import_data);
368
369 return leaf;
370 }
371 else
372 break;
373 }
374
375 Node symbols(NodeType::List);
376 setNodePosAndFilename(symbols);
377 // then parse the symbols to import, if any
378 if (space())
379 {
380 comment.clear();
382
383 while (!isEOF())
384 {
385 if (accept(IsChar(':'))) // parsing potential :a :b :c
386 {
387 std::string symbol_name;
388 if (!name(&symbol_name))
389 errorWithNextToken("Expected a valid symbol to import");
390 if (symbol_name == "*")
391 error(fmt::format("Glob patterns can not be separated from the package, use (import {}:*) instead", import_data.toPackageString()), symbol_name);
392
393 if (symbol_name.size() >= 2 && symbol_name[symbol_name.size() - 2] == ':' && symbol_name.back() == '*')
394 {
395 backtrack(getCount() - 2); // we can backtrack n-2 safely here because we know the previous chars were ":*"
396 error("Glob pattern can not follow a symbol to import", ":*");
397 }
398
399 symbols.push_back(Node(NodeType::Symbol, symbol_name).attachNearestCommentBefore(comment));
400 comment.clear();
401 setNodePosAndFilename(symbols.list().back());
402 import_data.symbols.push_back(symbol_name);
403 // we do not need the prefix when importing specific symbols
404 import_data.with_prefix = false;
405 }
406
407 if (!space())
408 break;
409 comment.clear();
411 }
412
413 if (!comment.empty() && !symbols.list().empty())
414 symbols.list().back().attachCommentAfter(comment);
415 }
416
417 leaf->push_back(packageNode);
418 leaf->push_back(symbols);
419 // save the import data
420 m_imports.push_back(import_data);
421
422 comment.clear();
424 leaf->list().back().attachCommentAfter(comment);
425
426 expectSuffixOrError(')', fmt::format("in import `{}'", import_data.toPackageString()), context);
427 return leaf;
428 }
429
430 std::optional<Node> Parser::block()
431 {
432 std::optional<Node> leaf { NodeType::List };
433 setNodePosAndFilename(leaf.value());
434
435 auto context = generateErrorContext("(");
436 bool alt_syntax = false;
437 std::string comment;
438 if (accept(IsChar('(')))
439 {
441 if (!oneOf({ "begin" }))
442 return std::nullopt;
443 }
444 else if (accept(IsChar('{')))
445 alt_syntax = true;
446 else
447 return std::nullopt;
448
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 backtrack(position);
730
732 const auto value = nodeOrValue();
734
735 if (value.has_value())
736 leaf->push_back(value.value());
737 else
738 errorWithNextToken(fmt::format("Expected an argument list, atom or node while defining macro `{}'", symbol_name));
739
740 setNodePosAndFilename(leaf->list().back());
741 if (accept(IsChar(')')))
742 return leaf;
743 }
744
746 const auto value = nodeOrValue();
748
749 if (value.has_value())
750 leaf->push_back(value.value());
751 else if (leaf->list().size() == 2)
752 {
753 setNodePosAndFilename(leaf->list().back());
754 comment.clear();
756 leaf->list().back().attachCommentAfter(comment);
757
758 expectSuffixOrError(')', fmt::format("to close macro `{}'", symbol_name), context);
759 return leaf;
760 }
761 else
762 {
763 backtrack(position);
764 errorWithNextToken(fmt::format("Expected a value while defining macro `{}'", symbol_name), context);
765 }
766
767 setNodePosAndFilename(leaf->list().back());
768 comment.clear();
770 leaf->list().back().attachCommentAfter(comment);
771
772 expectSuffixOrError(')', fmt::format("to close macro `{}'", symbol_name), context);
773 return leaf;
774 }
775
776 std::optional<Node> Parser::functionCall()
777 {
778 auto context = generateErrorContext("(");
779 if (!accept(IsChar('(')))
780 return std::nullopt;
781 std::string comment;
783 auto cursor = getCursor();
784
785 std::optional<Node> func;
786 if (auto sym_or_field = anyAtomOf({ NodeType::Symbol, NodeType::Field }); sym_or_field.has_value())
787 func = sym_or_field->attachNearestCommentBefore(comment);
788 else if (auto nested = node(); nested.has_value())
789 func = nested->attachNearestCommentBefore(comment);
790 else
791 return std::nullopt;
792 comment.clear();
794
795 std::optional<Node> leaf { NodeType::List };
796 setNodePosAndFilename(leaf.value(), cursor);
797 setNodePosAndFilename(func.value(), cursor);
798 leaf->push_back(func.value());
799
800 while (!isEOF())
801 {
802 if (auto arg = nodeOrValue(); arg.has_value())
803 {
804 leaf->push_back(arg.value().attachNearestCommentBefore(comment));
805 comment.clear();
807 }
808 else
809 break;
810 }
811
812 leaf->list().back().attachCommentAfter(comment);
813
814 comment.clear();
816 leaf->list().back().attachCommentAfter(comment);
817
818 expectSuffixOrError(')', fmt::format("in function call to `{}'", func.value().repr()), context);
819 return leaf;
820 }
821
822 std::optional<Node> Parser::list()
823 {
824 std::optional<Node> leaf { NodeType::List };
825 setNodePosAndFilename(leaf.value());
826
827 auto context = generateErrorContext("[");
828 if (!accept(IsChar('[')))
829 return std::nullopt;
830 leaf->push_back(Node(NodeType::Symbol, "list"));
831
832 std::string comment;
834 leaf->attachNearestCommentBefore(comment);
835
836 comment.clear();
837 while (!isEOF())
838 {
839 if (auto value = nodeOrValue(); value.has_value())
840 {
841 leaf->push_back(value.value().attachNearestCommentBefore(comment));
842 comment.clear();
844 }
845 else
846 break;
847 }
848 leaf->list().back().attachCommentAfter(comment);
849
850 expectSuffixOrError(']', "to end list definition", context);
851 return leaf;
852 }
853
854 std::optional<Node> Parser::atom()
855 {
856 const auto pos = getCount();
857
858 if (auto res = Parser::number(); res.has_value())
859 return res;
860 backtrack(pos);
861
862 if (auto res = Parser::string(); res.has_value())
863 return res;
864 backtrack(pos);
865
866 if (auto res = Parser::spread(); m_allow_macro_behavior > 0 && res.has_value())
867 return res;
868 backtrack(pos);
869
870 if (auto res = Parser::field(); res.has_value())
871 return res;
872 backtrack(pos);
873
874 if (auto res = Parser::symbol(); res.has_value())
875 return res;
876 backtrack(pos);
877
878 if (auto res = Parser::nil(); res.has_value())
879 return res;
880 backtrack(pos);
881
882 return std::nullopt;
883 }
884
885 std::optional<Node> Parser::anyAtomOf(const std::initializer_list<NodeType> types)
886 {
887 auto cursor = getCursor();
888 if (auto value = atom(); value.has_value())
889 {
890 setNodePosAndFilename(value.value(), cursor);
891 for (const auto type : types)
892 {
893 if (value->nodeType() == type)
894 return value;
895 }
896 }
897 return std::nullopt;
898 }
899
900 std::optional<Node> Parser::nodeOrValue()
901 {
902 auto cursor = getCursor();
903 if (auto value = atom(); value.has_value())
904 {
905 setNodePosAndFilename(value.value(), cursor);
906 return value;
907 }
908 if (auto sub_node = node(); sub_node.has_value())
909 {
910 setNodePosAndFilename(sub_node.value(), cursor);
911 return sub_node;
912 }
913
914 return std::nullopt;
915 }
916
917 std::optional<Node> Parser::wrapped(std::optional<Node> (Parser::*parser)(), const std::string& name)
918 {
919 auto cursor = getCursor();
920 auto context = generateErrorContext("(");
921 if (!prefix('('))
922 return std::nullopt;
923 std::string comment;
925
926 if (auto result = (this->*parser)(); result.has_value())
927 {
928 result->attachNearestCommentBefore(result->comment() + comment);
929 setNodePosAndFilename(result.value(), cursor);
930
931 comment.clear();
933 result.value().attachCommentAfter(comment);
934
935 if (result->isListLike())
936 setNodePosAndFilename(result->list().back());
937 expectSuffixOrError(')', "after " + name, context);
938
939 comment.clear();
940 if (spaceComment(&comment))
941 result.value().attachCommentAfter(comment);
942
943 return result;
944 }
945
946 return std::nullopt;
947 }
948}
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:74
A node of an Abstract Syntax Tree for ArkScript.
Definition Node.hpp:30
NodeType nodeType() const noexcept
Return the node type.
Definition Node.cpp:77
const std::string & string() const noexcept
Return the string held by the value (if the node type allows it)
Definition Node.cpp:37
void setPos(std::size_t line, std::size_t col) noexcept
Set the Position of the node in the text.
Definition Node.cpp:108
Node & attachNearestCommentBefore(const std::string &comment)
Set the comment field with the nearest comment before this node.
Definition Node.cpp:119
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:62
std::vector< Node > & list() noexcept
Return the list of sub-nodes held by the node.
Definition Node.cpp:67
std::vector< std::function< std::optional< Node >()> > m_parsers
Definition Parser.hpp:65
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:917
std::optional< Node > atom()
Try to parse an atom (number, string, spread, field, symbol, nil)
Definition Parser.cpp:854
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:243
std::optional< Node > symbol()
Definition Parser.hpp:223
std::optional< Node > number()
Definition Parser.hpp:90
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:900
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:106
std::optional< Node > macroCondition()
Definition Parser.cpp:594
std::optional< Node > spread()
Definition Parser.hpp:231
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:776
std::optional< Node > field()
Definition Parser.hpp:197
unsigned m_allow_macro_behavior
Toggled on when inside a macro definition, off afterward.
Definition Parser.hpp:63
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:885
std::vector< Import > m_imports
Definition Parser.hpp:62
std::optional< Node > function()
Definition Parser.cpp:524
std::optional< Node > list()
Definition Parser.cpp:822
std::size_t m_nested_nodes
Nested node counter.
Definition Parser.hpp:64
std::optional< Node > block()
Definition Parser.cpp:430
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