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

ArkScript-lang / Ark / 11087505602

28 Sep 2024 10:31PM UTC coverage: 75.218% (+1.6%) from 73.576%
11087505602

push

github

SuperFola
fix(json compiler): disable import solver in json compiler

1 of 1 new or added line in 1 file covered. (100.0%)

364 existing lines in 24 files now uncovered.

4841 of 6436 relevant lines covered (75.22%)

9644.12 hits per line

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

96.62
/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)
544✔
13
    {
544✔
14
        m_vars.emplace(name, is_mutable);
544✔
15
    }
544✔
16

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

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

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

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

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

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

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

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

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

74
    NameResolutionPass::NameResolutionPass(const unsigned debug) :
174✔
75
        Pass("NameResolution", debug),
87✔
76
        m_ast()
87✔
77
    {
174✔
78
        for (const auto& builtin : Builtins::builtins)
4,872✔
79
            m_language_symbols.emplace(builtin.first);
4,785✔
80
        for (auto ope : Language::operators)
2,088✔
81
            m_language_symbols.emplace(ope);
2,001✔
82
        for (auto inst : Language::listInstructions)
696✔
83
            m_language_symbols.emplace(inst);
609✔
84

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

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

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

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

108
    void NameResolutionPass::visit(const Node& node)
9,382✔
109
    {
9,382✔
110
        switch (node.nodeType())
9,382✔
111
        {
3,115✔
112
            case NodeType::Symbol:
113
                addSymbolNode(node);
3,115✔
114
                break;
3,329✔
115

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

121
            case NodeType::List:
122
                if (!node.constList().empty())
3,415✔
123
                {
124
                    if (node.constList()[0].nodeType() == NodeType::Keyword)
3,396✔
125
                        visitKeyword(node, node.constList()[0].keyword());
1,126✔
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,828✔
132
                            node.constList()[1].nodeType() == NodeType::Symbol)
1,558✔
133
                        {
134
                            const auto funcname = node.constList()[0].string();
568✔
135
                            const auto arg = node.constList()[1].string();
568✔
136

137
                            if (std::ranges::find(Language::UpdateRef, funcname) != Language::UpdateRef.end() && m_scope_resolver.isImmutable(arg).value_or(false))
568✔
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)
565✔
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
                        }
568✔
160

161
                        for (const auto& child : node.constList())
8,464✔
162
                            visit(child);
6,199✔
163
                    }
164
                }
3,391✔
165
                break;
6,048✔
166

167
            default:
168
                break;
2,638✔
169
        }
9,339✔
170
    }
9,344✔
171

172
    void NameResolutionPass::visitKeyword(const Node& node, const Keyword keyword)
1,126✔
173
    {
1,126✔
174
        switch (keyword)
1,126✔
175
        {
420✔
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)
420✔
184
                    visit(node.constList()[2]);
419✔
185
                if (node.constList().size() > 1 && node.constList()[1].nodeType() == NodeType::Symbol)
420✔
186
                {
187
                    const std::string& name = node.constList()[1].string();
418✔
188
                    if (m_language_symbols.contains(name))
418✔
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)
416✔
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)
415✔
204
                    {
205
                        const auto val = node.constList()[2].repr();
87✔
206

207
                        if (const auto mutability = m_scope_resolver.isImmutable(name); m_scope_resolver.isRegistered(name) &&
87✔
208
                            mutability.value_or(false))
172✔
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
                    }
87✔
216
                    else
217
                        addDefinedSymbol(name, keyword != Keyword::Let);
328✔
218
                }
420✔
219
                break;
420✔
220

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

226
            case Keyword::Fun:
227
                // create a new scope to track variables
228
                m_scope_resolver.createNew();
130✔
229
                if (node.constList()[1].nodeType() == NodeType::List)
130✔
230
                {
231
                    for (const auto& child : node.constList()[1].constList())
343✔
232
                    {
233
                        if (child.nodeType() == NodeType::Capture)
214✔
234
                        {
235
                            // First, check that the capture is a defined symbol
236
                            if (!m_defined_symbols.contains(child.string()))
43✔
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()))
42✔
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);
41✔
256
                        }
41✔
257
                        else if (child.nodeType() == NodeType::Symbol)
171✔
258
                            addDefinedSymbol(child.string(), /* is_mutable= */ true);
171✔
259
                    }
214✔
260
                }
127✔
261
                if (node.constList().size() > 2)
128✔
262
                    visit(node.constList()[2]);
127✔
263
                m_scope_resolver.removeLocalScope();  // and remove it once the function has been compiled
128✔
264
                break;
700✔
265

266
            default:
267
                for (const auto& child : node.constList())
3,131✔
268
                    visit(child);
2,559✔
269
                break;
572✔
270
        }
1,120✔
271
    }
1,126✔
272

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

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

281
        const auto it = std::ranges::find_if(m_symbol_nodes, [&name](const Node& sym_node) -> bool {
107,891✔
282
            return sym_node.string() == name;
106,127✔
283
        });
284
        if (it == m_symbol_nodes.end())
1,764✔
285
            m_symbol_nodes.push_back(symbol);
255✔
286
    }
3,548✔
287

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

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

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

310
                const std::string suggestion = offerSuggestion(str);
1✔
311
                if (suggestion.empty())
1✔
312
                    message = fmt::format(R"(Unbound variable error "{}" (variable is used but not defined))", str);
1✔
313
                else
UNCOV
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());
1✔
317
            }
1✔
318
        }
250✔
319
    }
48✔
320

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

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

337
        return suggestion;
1✔
338
    }
1✔
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

© 2026 Coveralls, Inc