ArkScript
A small, fast, functional and scripting language for video games
Function.cpp
Go to the documentation of this file.
1#include <cassert>
3
4#include <fmt/core.h>
5#include <ranges>
6
7#include <Ark/Constants.hpp>
8
9namespace Ark::internal
10{
12 {
13 return node.nodeType() == NodeType::List && !node.constList().empty() && node.constList()[0].nodeType() == NodeType::Symbol;
14 }
15
16 bool FunctionExecutor::applyMacro(Node& node, const unsigned depth)
17 {
18 const Node& first = node.list()[0];
19
20 // ($ name (args) body)
21 if (const Node* macro = findNearestMacro(first.string()); macro != nullptr && macro->constList().size() == 3)
22 {
23 Node temp_body = macro->constList()[2];
24 Node args = macro->constList()[1];
25 const std::size_t args_needed = args.list().size();
26 const std::size_t args_given = node.constList().size() - 1; // remove the first (the name of the macro)
27 const std::string macro_name = macro->constList()[0].string();
28 // thanks to the parser, we are guaranted that the spread will be in last position, if any
29 const bool has_spread = args_needed > 0 && args.list().back().nodeType() == NodeType::Spread;
30
31 // save the args given to the macro by giving them a name (from the macro args block),
32 // and a value (in nocde.constList())
33 std::unordered_map<std::string, Node> args_applied;
34 std::size_t j = 0;
35 for (std::size_t i = 1, end = node.constList().size(); i < end; ++i)
36 {
37 // by breaking early if we have too many arguments, the args_applied/args_needed check will fail
38 if (j >= args_needed)
39 break;
40
41 const std::string& arg_name = args.list()[j].string();
42 if (args.list()[j].nodeType() == NodeType::Symbol)
43 {
44 args_applied[arg_name] = node.constList()[i];
45 ++j;
46 }
47 else if (args.list()[j].nodeType() == NodeType::Spread)
48 {
49 if (!args_applied.contains(arg_name))
50 {
51 args_applied[arg_name] = Node(NodeType::List);
52 args_applied[arg_name].push_back(getListNode());
53 }
54 // do not move j because we checked before that the spread is always the last one
55 args_applied[arg_name].push_back(node.constList()[i]);
56 }
57 }
58
59 // check argument count
60 if (args_applied.size() + 1 == args_needed && has_spread)
61 {
62 // just a spread we didn't assign
63 args_applied[args.list().back().string()] = Node(NodeType::List);
64 args_applied[args.list().back().string()].push_back(getListNode());
65 }
66
67 if (args_given != args_needed && !has_spread)
68 throwMacroProcessingError(fmt::format("Macro `{}' got {} argument(s) but needed {}", macro_name, args_given, args_needed), node);
69 if (args_applied.size() != args_needed && has_spread)
70 // args_needed - 1 because we do not count the spread as a required argument
71 throwMacroProcessingError(fmt::format("Macro `{}' got {} argument(s) but needed at least {}", macro_name, args_applied.size(), args_needed - 1), node);
72
73 if (!args_applied.empty())
74 unify(args_applied, temp_body, nullptr);
75
76 node.updateValueAndType(evaluate(temp_body, depth + 1, false));
77 applyMacroProxy(node, depth + 1);
78 return true;
79 }
80
81 if (std::ranges::find(Language::macros, first.string()) != Language::macros.end())
82 {
83 node.updateValueAndType(evaluate(node, depth + 1, false));
84 return true;
85 }
86
87 return false;
88 }
89
90 void FunctionExecutor::unify(const std::unordered_map<std::string, Node>& map, Node& target, Node* parent, const std::size_t index, const std::size_t unify_depth)
91 {
92 if (unify_depth > MaxMacroUnificationDepth)
94 fmt::format(
95 "Max macro unification depth reached ({}). You may have a macro trying to evaluate itself, try splitting your code in multiple nodes.",
97 *parent);
98
99 if (target.nodeType() == NodeType::Symbol)
100 {
101 if (const auto p = map.find(target.string()); p != map.end())
102 target = p->second;
103 }
104 else if (target.isListLike())
105 {
106 for (std::size_t i = 0; i < target.list().size(); ++i)
107 unify(map, target.list()[i], &target, i, unify_depth + 1);
108 }
109 else if (target.nodeType() == NodeType::Spread)
110 {
111 assert(parent != nullptr && "Parent node should be defined when unifying a spread");
112
113 Node sub_node = target;
115 unify(map, sub_node, parent, 0, unify_depth + 1);
116
117 if (sub_node.nodeType() != NodeType::List)
118 throwMacroProcessingError(fmt::format("Can not unify a {} to a Spread", typeToString(sub_node)), sub_node);
119
120 for (std::size_t i = 1, end = sub_node.list().size(); i < end; ++i)
121 parent->list().insert(
122 parent->list().begin() + static_cast<std::vector<Node>::difference_type>(index + i),
123 sub_node.list()[i]);
124 // remove the spread
125 parent->list().erase(parent->list().begin() + static_cast<std::vector<Node>::difference_type>(index));
126 }
127 }
128}
Constants used by ArkScript.
Executor for List Macros.
bool canHandle(Node &node) override
Definition Function.cpp:11
bool applyMacro(Node &node, unsigned depth) override
Definition Function.cpp:16
void unify(const std::unordered_map< std::string, Node > &map, Node &target, Node *parent, std::size_t index=0, std::size_t unify_depth=0)
Definition Function.cpp:90
void applyMacroProxy(Node &node, unsigned depth)
Apply a macro on a given node.
Definition Executor.cpp:17
static void throwMacroProcessingError(const std::string &message, const Node &node)
Throw a macro processing error.
Definition Executor.cpp:37
Node evaluate(Node &node, unsigned depth, bool is_not_body) const
Evaluate only the macros.
Definition Executor.cpp:32
const Node * findNearestMacro(const std::string &name) const
Find the nearest macro matching a giving name.
Definition Executor.cpp:12
A node of an Abstract Syntax Tree for ArkScript.
Definition Node.hpp:31
NodeType nodeType() const noexcept
Return the node type.
Definition Node.cpp:77
void setNodeType(NodeType type) noexcept
Set the Node Type object.
Definition Node.cpp:98
bool isListLike() const noexcept
Check if the node is a list like node.
Definition Node.cpp:82
const std::string & string() const noexcept
Return the string held by the value (if the node type allows it)
Definition Node.cpp:37
const std::vector< Node > & constList() const noexcept
Return the list of sub-nodes held by the node.
Definition Node.cpp:72
void updateValueAndType(const Node &source) noexcept
Copy a node to the current one, while keeping the filename and position in the file.
Definition Node.cpp:92
std::vector< Node > & list() noexcept
Return the list of sub-nodes held by the node.
Definition Node.cpp:67
constexpr std::array macros
Definition Common.hpp:124
std::string typeToString(const Node &node) noexcept
Definition Node.hpp:241
const Node & getListNode()
Definition Node.cpp:326
constexpr std::size_t MaxMacroUnificationDepth
Controls the number of recursive calls to MacroProcessor::unify.
Definition Constants.hpp:67