• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ArkScript-lang / Ark / 11445793250

21 Oct 2024 05:51PM UTC coverage: 76.877% (+0.4%) from 76.499%
11445793250

push

github

SuperFola
refactor(macro): removing useless constructor

5130 of 6673 relevant lines covered (76.88%)

9379.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.61
/src/arkreactor/Compiler/NameResolutionPass.cpp
1
#include <Ark/Compiler/NameResolutionPass.hpp>
2

3
#include <Ark/Exceptions.hpp>
4
#include <Ark/Utils.hpp>
5
#include <Ark/Builtins/Builtins.hpp>
6

7
#include <fmt/format.h>
8
#include <ranges>
9

10
namespace Ark::internal
11
{
12
    void ScopeResolver::Scope::add(const std::string& name, bool is_mutable)
495✔
13
    {
495✔
14
        m_vars.emplace(name, is_mutable);
495✔
15
    }
495✔
16

17
    std::optional<Variable> ScopeResolver::Scope::get(const std::string& name) const
106✔
18
    {
106✔
19
        if (const auto it = std::ranges::find(m_vars, name, &Variable::name); it != m_vars.end())
198✔
20
            return *it;
92✔
21
        return std::nullopt;
14✔
22
    }
106✔
23

24
    bool ScopeResolver::Scope::has(const std::string& name) const
554✔
25
    {
554✔
26
        return std::ranges::find(m_vars, name, &Variable::name) != m_vars.end();
554✔
27
    }
28

29
    ScopeResolver::ScopeResolver()
88✔
30
    {
88✔
31
        createNew();
88✔
32
    }
88✔
33

34
    void ScopeResolver::createNew()
200✔
35
    {
200✔
36
        m_scopes.emplace_back();
200✔
37
    }
200✔
38

39
    void ScopeResolver::removeLocalScope()
110✔
40
    {
110✔
41
        m_scopes.pop_back();
110✔
42
    }
110✔
43

44
    void ScopeResolver::registerInCurrent(const std::string& name, const bool is_mutable)
495✔
45
    {
495✔
46
        m_scopes.back().add(name, is_mutable);
495✔
47
    }
495✔
48

49
    std::optional<bool> ScopeResolver::isImmutable(const std::string& name) const
94✔
50
    {
94✔
51
        for (const auto& m_scope : std::ranges::reverse_view(m_scopes))
200✔
52
        {
53
            if (auto maybe = m_scope.get(name); maybe.has_value())
198✔
54
                return !maybe.value().is_mutable;
92✔
55
        }
106✔
56
        return std::nullopt;
2✔
57
    }
94✔
58

59
    bool ScopeResolver::isRegistered(const std::string& name) const
119✔
60
    {
119✔
61
        return std::ranges::any_of(
119✔
62
            m_scopes.rbegin(),
119✔
63
            m_scopes.rend(),
119✔
64
            [name](const Scope& scope) {
403✔
65
                return scope.has(name);
165✔
66
            });
67
    }
×
68

69
    bool ScopeResolver::isInScope(const std::string& name) const
389✔
70
    {
389✔
71
        return m_scopes.back().has(name);
389✔
72
    }
73

74
    NameResolutionPass::NameResolutionPass(const unsigned debug) :
176✔
75
        Pass("NameResolution", debug),
88✔
76
        m_ast()
88✔
77
    {
176✔
78
        for (const auto& builtin : Builtins::builtins)
4,928✔
79
            m_language_symbols.emplace(builtin.first);
4,840✔
80
        for (auto ope : Language::operators)
2,112✔
81
            m_language_symbols.emplace(ope);
2,024✔
82
        for (auto inst : Language::listInstructions)
704✔
83
            m_language_symbols.emplace(inst);
616✔
84

85
        m_language_symbols.emplace(Language::And);
88✔
86
        m_language_symbols.emplace(Language::Or);
88✔
87
        m_language_symbols.emplace(Language::SysArgs);
88✔
88
    }
88✔
89

90
    void NameResolutionPass::process(const Node& ast)
50✔
91
    {
50✔
92
        m_ast = ast;
50✔
93
        visit(ast);
50✔
94
        checkForUndefinedSymbol();
50✔
95
    }
50✔
96

97
    const Node& NameResolutionPass::ast() const noexcept
×
98
    {
×
99
        return m_ast;
×
100
    }
101

102
    void NameResolutionPass::addDefinedSymbol(const std::string& sym, const bool is_mutable)
495✔
103
    {
495✔
104
        m_defined_symbols.emplace(sym);
495✔
105
        m_scope_resolver.registerInCurrent(sym, is_mutable);
495✔
106
    }
495✔
107

108
    void NameResolutionPass::visit(const Node& node)
8,980✔
109
    {
8,980✔
110
        switch (node.nodeType())
8,980✔
111
        {
2,997✔
112
            case NodeType::Symbol:
113
                addSymbolNode(node);
2,997✔
114
                break;
3,211✔
115

116
            case NodeType::Field:
117
                for (const auto& child : node.constList())
647✔
118
                    addSymbolNode(child);
433✔
119
                break;
3,477✔
120

121
            case NodeType::List:
122
                if (!node.constList().empty())
3,263✔
123
                {
124
                    if (node.constList()[0].nodeType() == NodeType::Keyword)
3,244✔
125
                        visitKeyword(node, node.constList()[0].keyword());
1,051✔
126
                    else
127
                    {
128
                        // function calls
129
                        // the UpdateRef function calls kind get a special treatment, like let/mut/set,
130
                        // because we need to check for mutability errors
131
                        if (node.constList().size() > 1 && node.constList()[0].nodeType() == NodeType::Symbol &&
3,666✔
132
                            node.constList()[1].nodeType() == NodeType::Symbol)
1,473✔
133
                        {
134
                            const auto funcname = node.constList()[0].string();
549✔
135
                            const auto arg = node.constList()[1].string();
549✔
136

137
                            if (std::ranges::find(Language::UpdateRef, funcname) != Language::UpdateRef.end() && m_scope_resolver.isImmutable(arg).value_or(false))
549✔
138
                                throw CodeError(
6✔
139
                                    fmt::format("MutabilityError: Can not modify the constant list `{}' using `{}'", arg, funcname),
3✔
140
                                    node.filename(),
3✔
141
                                    node.constList()[1].line(),
3✔
142
                                    node.constList()[1].col(),
3✔
143
                                    arg);
3✔
144

145
                            // check that we aren't doing a (append! a a) nor a (concat! a a)
146
                            if (funcname == Language::AppendInPlace || funcname == Language::ConcatInPlace)
546✔
147
                            {
148
                                for (std::size_t i = 2, end = node.constList().size(); i < end; ++i)
12✔
149
                                {
150
                                    if (node.constList()[i].nodeType() == NodeType::Symbol && node.constList()[i].string() == arg)
6✔
151
                                        throw CodeError(
4✔
152
                                            fmt::format("MutabilityError: Can not {} the list `{}' to itself", funcname, arg),
2✔
153
                                            node.filename(),
2✔
154
                                            node.constList()[1].line(),
2✔
155
                                            node.constList()[1].col(),
2✔
156
                                            arg);
2✔
157
                                }
4✔
158
                            }
4✔
159
                        }
549✔
160

161
                        for (const auto& child : node.constList())
8,149✔
162
                            visit(child);
5,961✔
163
                    }
164
                }
3,239✔
165
                break;
5,764✔
166

167
            default:
168
                break;
2,506✔
169
        }
8,937✔
170
    }
8,942✔
171

172
    void NameResolutionPass::visitKeyword(const Node& node, const Keyword keyword)
1,051✔
173
    {
1,051✔
174
        switch (keyword)
1,051✔
175
        {
393✔
176
            case Keyword::Set:
177
                [[fallthrough]];
178
            case Keyword::Let:
179
                [[fallthrough]];
180
            case Keyword::Mut:
181
                // first, visit the value, then register the symbol
182
                // this allows us to detect things like (let foo (fun (&foo) ()))
183
                if (node.constList().size() > 2)
393✔
184
                    visit(node.constList()[2]);
392✔
185
                if (node.constList().size() > 1 && node.constList()[1].nodeType() == NodeType::Symbol)
393✔
186
                {
187
                    const std::string& name = node.constList()[1].string();
391✔
188
                    if (m_language_symbols.contains(name))
391✔
189
                        throw CodeError(
4✔
190
                            fmt::format("Can not use a reserved identifier ('{}') as a {} name.", name, keyword == Keyword::Let ? "constant" : "variable"),
2✔
191
                            node.filename(),
2✔
192
                            node.constList()[1].line(),
2✔
193
                            node.constList()[1].col(),
2✔
194
                            name);
2✔
195

196
                    if (m_scope_resolver.isInScope(name) && keyword == Keyword::Let)
389✔
197
                        throw CodeError(
2✔
198
                            fmt::format("MutabilityError: Can not use 'let' to redefine variable `{}'", name),
1✔
199
                            node.filename(),
1✔
200
                            node.constList()[1].line(),
1✔
201
                            node.constList()[1].col(),
1✔
202
                            name);
1✔
203
                    else if (keyword == Keyword::Set)
388✔
204
                    {
205
                        const auto val = node.constList()[2].repr();
82✔
206

207
                        if (const auto mutability = m_scope_resolver.isImmutable(name); m_scope_resolver.isRegistered(name) &&
82✔
208
                            mutability.value_or(false))
162✔
209
                            throw CodeError(
2✔
210
                                fmt::format("MutabilityError: Can not set the constant `{}' to {}", name, val),
1✔
211
                                node.filename(),
1✔
212
                                node.constList()[1].line(),
1✔
213
                                node.constList()[1].col(),
1✔
214
                                name);
1✔
215
                    }
82✔
216
                    else
217
                        addDefinedSymbol(name, keyword != Keyword::Let);
306✔
218
                }
393✔
219
                break;
389✔
220

221
            case Keyword::Import:
222
                if (!node.constList().empty())
×
223
                    m_plugin_names.push_back(node.constList()[1].constList().back().string());
×
224
                break;
112✔
225

226
            case Keyword::Fun:
227
                // create a new scope to track variables
228
                m_scope_resolver.createNew();
112✔
229
                if (node.constList()[1].nodeType() == NodeType::List)
112✔
230
                {
231
                    for (const auto& child : node.constList()[1].constList())
298✔
232
                    {
233
                        if (child.nodeType() == NodeType::Capture)
187✔
234
                        {
235
                            // First, check that the capture is a defined symbol
236
                            if (!m_defined_symbols.contains(child.string()))
38✔
237
                            {
238
                                // we didn't find node in the defined symbol list, thus we can't capture node
239
                                throw CodeError(
2✔
240
                                    fmt::format("Can not capture {} because it is referencing an unbound variable.", child.string()),
1✔
241
                                    child.filename(),
1✔
242
                                    child.line(),
1✔
243
                                    child.col(),
1✔
244
                                    child.repr());
1✔
245
                            }
246
                            else if (!m_scope_resolver.isRegistered(child.string()))
37✔
247
                            {
248
                                throw CodeError(
2✔
249
                                    fmt::format("Can not capture {} because it is referencing a variable defined in an unreachable scope.", child.string()),
1✔
250
                                    child.filename(),
1✔
251
                                    child.line(),
1✔
252
                                    child.col(),
1✔
253
                                    child.repr());
1✔
254
                            }
255
                            addDefinedSymbol(child.string(), /* is_mutable= */ true);
36✔
256
                        }
36✔
257
                        else if (child.nodeType() == NodeType::Symbol)
149✔
258
                            addDefinedSymbol(child.string(), /* is_mutable= */ true);
149✔
259
                    }
187✔
260
                }
109✔
261
                if (node.constList().size() > 2)
110✔
262
                    visit(node.constList()[2]);
109✔
263
                m_scope_resolver.removeLocalScope();  // and remove it once the function has been compiled
110✔
264
                break;
656✔
265

266
            default:
267
                for (const auto& child : node.constList())
2,995✔
268
                    visit(child);
2,449✔
269
                break;
546✔
270
        }
1,045✔
271
    }
1,051✔
272

273
    void NameResolutionPass::addSymbolNode(const Node& symbol)
3,430✔
274
    {
3,430✔
275
        const std::string& name = symbol.string();
3,430✔
276

277
        // we don't accept builtins/operators as a user symbol
278
        if (m_language_symbols.contains(name))
3,430✔
279
            return;
1,723✔
280

281
        const auto it = std::ranges::find_if(m_symbol_nodes, [&name](const Node& sym_node) -> bool {
108,245✔
282
            return sym_node.string() == name;
106,538✔
283
        });
284
        if (it == m_symbol_nodes.end())
1,707✔
285
            m_symbol_nodes.push_back(symbol);
214✔
286
    }
3,430✔
287

288
    bool NameResolutionPass::mayBeFromPlugin(const std::string& name) const noexcept
214✔
289
    {
214✔
290
        std::string splitted = Utils::splitString(name, ':')[0];
214✔
291
        const auto it = std::ranges::find_if(
214✔
292
            m_plugin_names,
214✔
293
            [&splitted](const std::string& plugin) -> bool {
214✔
294
                return plugin == splitted;
×
295
            });
296
        return it != m_plugin_names.end();
214✔
297
    }
214✔
298

299
    void NameResolutionPass::checkForUndefinedSymbol() const
39✔
300
    {
39✔
301
        for (const auto& sym : m_symbol_nodes)
253✔
302
        {
303
            const auto& str = sym.string();
214✔
304
            const bool is_plugin = mayBeFromPlugin(str);
214✔
305

306
            if (!m_defined_symbols.contains(str) && !is_plugin)
214✔
307
            {
308
                std::string message;
×
309

310
                const std::string suggestion = offerSuggestion(str);
×
311
                if (suggestion.empty())
×
312
                    message = fmt::format(R"(Unbound variable error "{}" (variable is used but not defined))", str);
×
313
                else
314
                    message = fmt::format(R"(Unbound variable error "{}" (did you mean "{}"?))", str, suggestion);
×
315

316
                throw CodeError(message, sym.filename(), sym.line(), sym.col(), sym.repr());
×
317
            }
×
318
        }
214✔
319
    }
39✔
320

321
    std::string NameResolutionPass::offerSuggestion(const std::string& str) const
×
322
    {
×
323
        std::string suggestion;
×
324
        // our suggestion shouldn't require more than half the string to change
325
        std::size_t suggestion_distance = str.size() / 2;
×
326

327
        for (const std::string& symbol : m_defined_symbols)
×
328
        {
329
            const std::size_t current_distance = Utils::levenshteinDistance(str, symbol);
×
330
            if (current_distance <= suggestion_distance)
×
331
            {
332
                suggestion_distance = current_distance;
×
333
                suggestion = symbol;
×
334
            }
×
335
        }
×
336

337
        return suggestion;
×
338
    }
×
339
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc