ArkScript
A small, lisp-inspired, functional scripting language
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 // (macro 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 guaranteed 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 node.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 // todo: this fails because we don't have a string because we are defining a (macro ! (call ...args) ...) inside another (macro ! (call ...args) ...)
42 // most likely the first macro got applied to another macro. We shouldn't apply macro on macros
43 // assert(args.list()[j].nodeType() == NodeType::String || args.list()[j].nodeType() == NodeType::Spread); // todo: temp
44 const std::string& arg_name = args.list()[j].string();
45 if (args.list()[j].nodeType() == NodeType::Symbol)
46 {
47 args_applied[arg_name] = node.constList()[i];
48 ++j;
49 }
50 else if (args.list()[j].nodeType() == NodeType::Spread)
51 {
52 if (!args_applied.contains(arg_name))
53 {
54 args_applied[arg_name] = Node(NodeType::List);
55 args_applied[arg_name].push_back(getListNode());
56 }
57 // do not move j because we checked before that the spread is always the last one
58 args_applied[arg_name].push_back(node.constList()[i]);
59 }
60 }
61
62 // check argument count
63 if (args_applied.size() + 1 == args_needed && has_spread)
64 {
65 // just a spread we didn't assign
66 args_applied[args.list().back().string()] = Node(NodeType::List);
67 args_applied[args.list().back().string()].push_back(getListNode());
68 }
69
70 if (args_given != args_needed && !has_spread)
71 throwMacroProcessingError(fmt::format("Macro `{}' got {} argument(s) but needed {}", macro_name, args_given, args_needed), node);
72 if (args_applied.size() != args_needed && has_spread)
73 // args_needed - 1 because we do not count the spread as a required argument
74 throwMacroProcessingError(fmt::format("Macro `{}' got {} argument(s) but needed at least {}", macro_name, args_applied.size(), args_needed - 1), node);
75
76 if (!args_applied.empty())
77 unify(args_applied, temp_body, nullptr);
78
79 node.updateValueAndType(evaluate(temp_body, depth + 1, false));
80 applyMacroProxy(node, depth + 1);
81 return true;
82 }
83
84 if (std::ranges::find(Language::macros, first.string()) != Language::macros.end())
85 {
86 node.updateValueAndType(evaluate(node, depth + 1, false));
87 return true;
88 }
89
90 return false;
91 }
92
94 {
95 const Node& first = node.list()[0];
96 if (const Node* macro = findNearestMacro(first.string()); macro != nullptr && macro->constList().size() == 3)
97 return *macro;
98 return {};
99 }
100
101 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)
102 {
103 if (unify_depth > MaxMacroUnificationDepth)
105 fmt::format(
106 "Max macro unification depth reached ({}). You may have a macro trying to evaluate itself, try splitting your code in multiple nodes.",
108 *parent);
109
110 if (target.nodeType() == NodeType::Symbol)
111 {
112 if (const auto p = map.find(target.string()); p != map.end())
113 target = p->second;
114 }
115 else if (target.isListLike())
116 {
117 if (target.nodeType() == NodeType::Macro && target.list()[0].nodeType() == NodeType::Symbol)
118 {
119 if (const std::string macro_name = target.list()[0].string(); map.contains(macro_name))
121 fmt::format(
122 "Can not define a macro by reusing the argument name `{}'",
123 macro_name),
124 target);
125
126 // proceed for expansion only on the value of each macro
127 unify(map, target.list().back(), &target, target.list().size() - 1, unify_depth + 1);
128 }
129 else
130 {
131 // proceed for expansion on normal nodes, we can safely run on all subnodes
132 for (std::size_t i = 0; i < target.list().size(); ++i)
133 unify(map, target.list()[i], &target, i, unify_depth + 1);
134 }
135 }
136 else if (target.nodeType() == NodeType::Spread)
137 {
138 assert(parent != nullptr && "Parent node should be defined when unifying a spread");
139
140 Node sub_node = target;
142 unify(map, sub_node, parent, 0, unify_depth + 1);
143
144 if (sub_node.nodeType() != NodeType::List)
145 parent->list()[index] = sub_node;
146 else
147 {
148 const bool is_list = sub_node.list().front() == getListNode();
149
150 for (std::size_t i = is_list ? 1 : 0, end = sub_node.list().size(); i < end; ++i)
151 parent->list().insert(
152 parent->list().begin() + static_cast<std::vector<Node>::difference_type>(index + i + (is_list ? 0 : 1)),
153 sub_node.list()[i]);
154 // remove the spread
155 parent->list().erase(parent->list().begin() + static_cast<std::vector<Node>::difference_type>(index));
156 }
157 }
158 }
159}
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
Node macroNode(Node &node) override
Returns the macro node that will be expanded.
Definition Function.cpp:93
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:101
void applyMacroProxy(Node &node, unsigned depth)
Apply a macro on a given node.
Definition Executor.cpp:17
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:32
NodeType nodeType() const noexcept
Return the node type.
Definition Node.cpp:78
void setNodeType(NodeType type) noexcept
Set the Node Type object.
Definition Node.cpp:107
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
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:101
std::vector< Node > & list() noexcept
Return the list of sub-nodes held by the node.
Definition Node.cpp:68
constexpr std::array macros
Definition Common.hpp:140
const Node & getListNode()
Definition Node.cpp:372
constexpr std::size_t MaxMacroUnificationDepth
Controls the number of recursive calls to MacroProcessor::unify.
Definition Constants.hpp:74