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

ArkScript-lang / Ark / 12164232004

04 Dec 2024 04:37PM UTC coverage: 77.815% (+0.6%) from 77.19%
12164232004

push

github

SuperFola
feat(name resolution): allow fqn if the scope is only exporting symbols

1 of 3 new or added lines in 2 files covered. (33.33%)

256 existing lines in 11 files now uncovered.

5412 of 6955 relevant lines covered (77.81%)

9300.67 hits per line

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

82.71
/src/arkreactor/Compiler/Package/ImportSolver.cpp
1
#include <Ark/Compiler/Package/ImportSolver.hpp>
2

3
#include <ranges>
4
#include <algorithm>
5
#include <fmt/core.h>
6

7
#include <Ark/Files.hpp>
8
#include <Ark/Exceptions.hpp>
9
#include <Ark/Compiler/AST/Parser.hpp>
10

11
namespace Ark::internal
12
{
13
    ImportSolver::ImportSolver(const unsigned debug, const std::vector<std::filesystem::path>& libenv) :
345✔
14
        Pass("ImportSolver", debug), m_debug_level(debug), m_libenv(libenv), m_ast()
115✔
15
    {}
345✔
16

17
    ImportSolver& ImportSolver::setup(const std::filesystem::path& root, const std::vector<Import>& origin_imports)
87✔
18
    {
87✔
19
        if (is_directory(root))
87✔
20
            m_root = root;
4✔
21
        else
22
            m_root = root.parent_path();
83✔
23

24
        for (const auto& origin_import : std::ranges::reverse_view(origin_imports))
103✔
25
            m_imports.push(origin_import);
16✔
26

27
        return *this;
87✔
28
    }
29

30
    void ImportSolver::process(const Node& origin_ast)
87✔
31
    {
87✔
32
        m_logger.traceStart("process");
87✔
33

34
        while (!m_imports.empty())
113✔
35
        {
36
            Import import = m_imports.top();
26✔
37
            m_logger.debug("Importing {}", import.toPackageString());
26✔
38

39
            // Remove the top element to process the other imports
40
            // It needs to be removed first because we might be adding
×
41
            // other imports later and don't want to pop THEM
42
            m_imports.pop();
26✔
43
            const auto package = import.toPackageString();
26✔
44

45
            if (m_packages.contains(package))
26✔
46
            {
47
                // merge the definition, so that we can generate valid Full Qualified Names in the name & scope resolver
48
                m_packages[package].import.with_prefix |= import.with_prefix;
6✔
49
                m_packages[package].import.is_glob |= import.is_glob;
6✔
50
                for (auto&& symbol : import.symbols)
6✔
UNCOV
51
                    m_packages[package].import.symbols.push_back(symbol);
×
52
            }
6✔
53
            else
54
            {
55
                // NOTE: since the "file" (=root) argument doesn't change between all calls, we could get rid of it
56
                std::vector<Import> temp = parseImport(m_root, import);
20✔
57
                for (auto& additional_import : std::ranges::reverse_view(temp))
30✔
58
                    m_imports.push(additional_import);
10✔
59
            }
20✔
60
        }
26✔
61

62
        m_logger.traceStart("findAndReplaceImports");
87✔
63
        m_ast = findAndReplaceImports(origin_ast).first;
87✔
64
        m_logger.traceEnd();
87✔
65

66
        m_logger.traceEnd();
87✔
67
    }
87✔
68

69
    std::pair<Node, bool> ImportSolver::findAndReplaceImports(const Node& ast)
6,534✔
70
    {
6,534✔
71
        Node x = ast;
6,534✔
72
        if (x.nodeType() == NodeType::List)
6,534✔
73
        {
74
            if (x.constList().size() >= 2 && x.constList()[0].nodeType() == NodeType::Keyword &&
2,943✔
75
                x.constList()[0].keyword() == Keyword::Import)
815✔
76
            {
77
                // compute the package string: foo.bar.egg
78
                const auto import_node = x.constList()[1].constList();
26✔
79
                const std::string package = std::accumulate(
52✔
80
                    std::next(import_node.begin()),
26✔
81
                    import_node.end(),
26✔
82
                    import_node[0].string(),
26✔
83
                    [](const std::string& acc, const Node& elem) -> std::string {
11✔
84
                        return acc + "." + elem.string();
11✔
UNCOV
85
                    });
×
86

87
                // if it wasn't imported already, register it
88
                if (std::ranges::find(m_imported, package) == m_imported.end())
26✔
89
                {
90
                    m_imported.push_back(package);
20✔
91
                    // modules are already handled, we can safely replace the node
92
                    x = m_packages[package].ast;
20✔
93
                    if (!m_packages[package].has_been_processed)
20✔
94
                    {
95
                        const auto import = m_packages[package].import;
20✔
96

97
                        // prefix to lowercase ; usually considered unsafe (https://devblogs.microsoft.com/oldnewthing/20241007-00/?p=110345)
98
                        // but we are dealing with prefix from filenames, thus we can somewhat assume we are in safe zone
99
                        std::string prefix = import.prefix;
20✔
100
                        std::ranges::transform(
20✔
101
                            prefix, prefix.begin(),
20✔
102
                            [](auto c) {
101✔
103
                                return std::tolower(c);
101✔
104
                            });
105

106
                        x = Node(Namespace {
80✔
107
                            .name = prefix,
20✔
108
                            .is_glob = import.is_glob,
20✔
109
                            .with_prefix = import.with_prefix,
20✔
110
                            .symbols = import.symbols,
20✔
111
                            .ast = std::make_shared<Node>(findAndReplaceImports(x).first) });
20✔
112
                    }
20✔
113
                    // we parsed an import node, return true in the pair to notify the caller
114
                    return std::make_pair(x, /* is_import= */ true);
20✔
115
                }
116

117
                // Replace by empty node to avoid breaking the code gen
118
                x = Node(NodeType::List);
6✔
119
                x.push_back(Node(Keyword::Begin));
6✔
120
            }
26✔
121
            else
122
            {
123
                for (std::size_t i = 0; i < x.constList().size(); ++i)
8,529✔
124
                {
125
                    auto [node, is_import] = findAndReplaceImports(x.constList()[i]);
6,427✔
126
                    x.list()[i] = node;
6,427✔
127
                }
6,427✔
UNCOV
128
            }
×
129
        }
2,108✔
130

131
        return std::make_pair(x, /* is_import= */ false);
6,514✔
132
    }
6,534✔
133

134
    const Node& ImportSolver::ast() const noexcept
87✔
135
    {
87✔
136
        return m_ast;
87✔
137
    }
138

139
    std::vector<Import> ImportSolver::parseImport(const std::filesystem::path& base_path, const Import& import)
20✔
140
    {
20✔
141
        m_logger.traceStart(fmt::format("parseImport {}", base_path.string()));
20✔
142

143
        const auto path = findFile(base_path, import);
20✔
144
        if (path.extension() == ".arkm")  // Nothing to import in case of modules
20✔
145
        {
146
            // Creating an import node that will stay there when visiting the AST and
147
            // replacing the imports with their parsed module
UNCOV
148
            auto module_node = Node(NodeType::List);
×
UNCOV
149
            module_node.push_back(Node(Keyword::Import));
×
150

UNCOV
151
            auto package_node = Node(NodeType::List);
×
UNCOV
152
            std::ranges::transform(import.package, std::back_inserter(package_node.list()), [](const std::string& stem) {
×
UNCOV
153
                return Node(NodeType::String, stem);
×
154
            });
UNCOV
155
            module_node.push_back(package_node);
×
156
            // empty symbols list
157
            module_node.push_back(Node(NodeType::List));
×
158

159
            m_packages[import.toPackageString()] = Package {
1✔
UNCOV
160
                module_node,
×
161
                import,
×
162
                true
163
            };
164

165
            return {};
×
UNCOV
166
        }
×
167

168
        Parser parser(m_debug_level);
20✔
169
        const std::string code = Utils::readFile(path.generic_string());
20✔
170
        parser.process(path.string(), code);
20✔
171
        m_packages[import.toPackageString()] = Package {
60✔
172
            parser.ast(),
20✔
173
            import,
20✔
174
            false
175
        };
176

177
        m_logger.traceEnd();
20✔
178
        return parser.imports();
20✔
179
    }
20✔
180

181
    std::optional<std::filesystem::path> testExtensions(const std::filesystem::path& folder, const std::string& package_path)
23✔
182
    {
23✔
183
        if (auto code_path = folder / (package_path + ".ark"); std::filesystem::exists(code_path))
43✔
184
            return code_path;
20✔
185
        if (auto module_path = folder / (package_path + ".arkm"); std::filesystem::exists(module_path))
3✔
UNCOV
186
            return module_path;
×
187
        return {};
3✔
188
    }
23✔
189

190
    std::filesystem::path ImportSolver::findFile(const std::filesystem::path& file, const Import& import) const
20✔
191
    {
20✔
192
        const std::string package_path = import.packageToPath();
20✔
193
        if (auto maybe_path = testExtensions(m_root, package_path); maybe_path.has_value())
37✔
194
            return maybe_path.value();
17✔
195

196
        // search in all folders in environment path
197
        for (const auto& path : m_libenv)
3✔
198
        {
199
            if (auto maybe_path = testExtensions(path, package_path); maybe_path.has_value())
6✔
200
                return maybe_path.value();
3✔
201
        }
3✔
202

203
        // fallback, we couldn't find the file
UNCOV
204
        throw CodeError(
×
UNCOV
205
            fmt::format("While processing file {}, couldn't import {}: file not found",
×
UNCOV
206
                        file.generic_string(), import.toPackageString()),
×
UNCOV
207
            file.generic_string(),
×
UNCOV
208
            import.line,
×
UNCOV
209
            import.col,
×
UNCOV
210
            fmt::format("(import {})", import.toPackageString()));
×
211
    }
20✔
212
}
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