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

ArkScript-lang / Ark / 20369834167

19 Dec 2025 12:17PM UTC coverage: 90.661% (-0.1%) from 90.758%
20369834167

push

github

SuperFola
feat(ir optimizer, vm): new super instructions MUL_BY, MUL_BY_INDEX and MUL_SET_VAL

69 of 86 new or added lines in 2 files covered. (80.23%)

3 existing lines in 1 file now uncovered.

8193 of 9037 relevant lines covered (90.66%)

239488.84 hits per line

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

97.83
/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/Utils/Files.hpp>
8
#include <Ark/Error/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) :
1,050✔
14
        Pass("ImportSolver", debug), m_debug_level(debug), m_libenv(libenv), m_ast()
350✔
15
    {}
1,050✔
16

17
    ImportSolver& ImportSolver::setup(const std::filesystem::path& root, const std::vector<Import>& origin_imports)
314✔
18
    {
314✔
19
        // keep the given root if it's a directory, it means it comes from a code string evaluation in the state, where we don't have a filename
20
        m_root = is_directory(root) ? root : root.parent_path();
314✔
21

22
        for (const auto& origin_import : std::ranges::reverse_view(origin_imports))
423✔
23
            m_imports.push({ root, origin_import });
109✔
24

25
        return *this;
314✔
UNCOV
26
    }
×
27

28
    void ImportSolver::process(const Node& origin_ast)
314✔
29
    {
314✔
30
        m_logger.traceStart("process");
315✔
31

32
        while (!m_imports.empty())
501✔
33
        {
34
            ImportWithSource source = m_imports.top();
188✔
35
            m_logger.debug("Importing {}", source.import.toPackageString());
188✔
36

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

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

60
        m_logger.traceStart("findAndReplaceImports");
313✔
61
        m_ast = findAndReplaceImports(origin_ast).first;
313✔
62
        m_logger.traceEnd();
313✔
63

64
        m_logger.traceEnd();
313✔
65
    }
314✔
66

67
    std::pair<Node, bool> ImportSolver::findAndReplaceImports(const Node& ast)
195,717✔
68
    {
195,717✔
69
        Node x = ast;
195,717✔
70
        if (x.nodeType() == NodeType::List)
195,717✔
71
        {
72
            if (x.constList().size() >= 2 && x.constList()[0].nodeType() == NodeType::Keyword &&
61,670✔
73
                x.constList()[0].keyword() == Keyword::Import)
19,162✔
74
            {
75
                // compute the package string: foo.bar.egg
76
                const auto import_node = x.constList()[1].constList();
187✔
77
                const std::string package = std::accumulate(
374✔
78
                    std::next(import_node.begin()),
187✔
79
                    import_node.end(),
187✔
80
                    import_node[0].string(),
187✔
81
                    [](const std::string& acc, const Node& elem) -> std::string {
139✔
82
                        return acc + "." + elem.string();
139✔
UNCOV
83
                    });
×
84

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

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

104
                        x = Node(Namespace {
560✔
105
                            .name = prefix,
140✔
106
                            .is_glob = import.is_glob,
140✔
107
                            .with_prefix = import.with_prefix,
140✔
108
                            .symbols = import.symbols,
140✔
109
                            .ast = std::make_shared<Node>(findAndReplaceImports(x).first) });
140✔
110

111
                        x.arkNamespace().ast->setPositionFrom(ast);
140✔
112
                    }
140✔
113
                    // we parsed an import node, return true in the pair to notify the caller
114
                    return std::make_pair(x, /* is_import= */ true);
142✔
115
                }
116

117
                // Replace by empty node to avoid breaking the code gen
118
                x = Node(NodeType::List);
45✔
119
                x.push_back(Node(Keyword::Begin));
45✔
120
            }
187✔
121
            else
122
            {
123
                for (std::size_t i = 0; i < x.constList().size(); ++i)
237,585✔
124
                {
125
                    auto [node, is_import] = findAndReplaceImports(x.constList()[i]);
195,264✔
126
                    x.list()[i] = node;
195,264✔
127
                }
195,264✔
128
            }
129
        }
42,366✔
130

131
        return std::make_pair(x, /* is_import= */ false);
195,575✔
132
    }
195,717✔
133

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

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

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

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

161
            m_packages[import.toPackageString()] = Package {
4✔
162
                module_node,
2✔
163
                import,
2✔
164
                true
165
            };
166

167
            return {};
2✔
168
        }
2✔
169

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

179
        m_logger.traceEnd();
140✔
180

181
        auto imports = parser.imports();
140✔
182
        std::vector<ImportWithSource> output;
140✔
183
        std::ranges::transform(
140✔
184
            imports,
185
            std::back_inserter(output), [&path](const Import& i) {
219✔
186
                return ImportWithSource { path, i };
79✔
UNCOV
187
            });
×
188
        return output;
140✔
189
    }
142✔
190

191
    std::optional<std::filesystem::path> testExtensions(const std::filesystem::path& folder, const std::string& package_path)
237✔
192
    {
237✔
193
        if (auto code_path = folder / (package_path + ".ark"); std::filesystem::exists(code_path))
377✔
194
            return code_path;
140✔
195
        if (auto module_path = folder / (package_path + ".arkm"); std::filesystem::exists(module_path))
99✔
196
            return module_path;
2✔
197
        return {};
95✔
198
    }
237✔
199

200
    std::filesystem::path ImportSolver::findFile(const std::filesystem::path& file, const Import& import_) const
143✔
201
    {
143✔
202
        const std::string package_path = import_.packageToPath();
143✔
203
        if (auto maybe_path = testExtensions(m_root, package_path); maybe_path.has_value())
193✔
204
            return maybe_path.value();
50✔
205

206
        // search in all folders in environment path
207
        for (const auto& path : m_libenv)
95✔
208
        {
209
            if (auto maybe_path = testExtensions(path, package_path); maybe_path.has_value())
186✔
210
                return maybe_path.value();
92✔
211
        }
94✔
212

213
        // fallback, we couldn't find the file
214
        throw CodeError(
2✔
215
            fmt::format("While processing file {}, couldn't import {}: file not found",
2✔
216
                        file.filename().string(), import_.toPackageString()),
1✔
217
            CodeErrorContext(
1✔
218
                file.generic_string(),
1✔
219
                FileSpan { .start = FilePos { .line = import_.line, .column = import_.col }, .end = std::nullopt }));
1✔
220
    }
144✔
221
}
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