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

ArkScript-lang / Ark / 15006215616

13 May 2025 08:34PM UTC coverage: 86.726% (+0.3%) from 86.474%
15006215616

push

github

SuperFola
feat(macro processor, error): adding better error messages when a macro fails, to show the macro we were expanding and what failed

60 of 60 new or added lines in 8 files covered. (100.0%)

83 existing lines in 6 files now uncovered.

7017 of 8091 relevant lines covered (86.73%)

79023.01 hits per line

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

97.87
/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) :
816✔
14
        Pass("ImportSolver", debug), m_debug_level(debug), m_libenv(libenv), m_ast()
272✔
15
    {}
816✔
16

17
    ImportSolver& ImportSolver::setup(const std::filesystem::path& root, const std::vector<Import>& origin_imports)
242✔
18
    {
242✔
19
        m_root = root.parent_path();
242✔
20

21
        for (const auto& origin_import : std::ranges::reverse_view(origin_imports))
310✔
22
            m_imports.push({ root, origin_import });
68✔
23

24
        return *this;
242✔
UNCOV
25
    }
×
26

27
    void ImportSolver::process(const Node& origin_ast)
242✔
28
    {
242✔
29
        m_logger.traceStart("process");
243✔
30

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

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

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

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

63
        m_logger.traceEnd();
241✔
64
    }
242✔
65

66
    std::pair<Node, bool> ImportSolver::findAndReplaceImports(const Node& ast)
115,463✔
67
    {
115,463✔
68
        Node x = ast;
115,463✔
69
        if (x.nodeType() == NodeType::List)
115,463✔
70
        {
71
            if (x.constList().size() >= 2 && x.constList()[0].nodeType() == NodeType::Keyword &&
24,223✔
72
                x.constList()[0].keyword() == Keyword::Import)
7,480✔
73
            {
74
                // compute the package string: foo.bar.egg
75
                const auto import_node = x.constList()[1].constList();
119✔
76
                const std::string package = std::accumulate(
238✔
77
                    std::next(import_node.begin()),
119✔
78
                    import_node.end(),
119✔
79
                    import_node[0].string(),
119✔
80
                    [](const std::string& acc, const Node& elem) -> std::string {
86✔
81
                        return acc + "." + elem.string();
86✔
UNCOV
82
                    });
×
83

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

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

103
                        x = Node(Namespace {
372✔
104
                            .name = prefix,
93✔
105
                            .is_glob = import.is_glob,
93✔
106
                            .with_prefix = import.with_prefix,
93✔
107
                            .symbols = import.symbols,
93✔
108
                            .ast = std::make_shared<Node>(findAndReplaceImports(x).first) });
93✔
109
                        x.arkNamespace().ast->setPos(ast.line(), ast.col());
93✔
110
                        x.arkNamespace().ast->setFilename(ast.filename());
93✔
111
                    }
93✔
112
                    // we parsed an import node, return true in the pair to notify the caller
113
                    return std::make_pair(x, /* is_import= */ true);
94✔
114
                }
115

116
                // Replace by empty node to avoid breaking the code gen
117
                x = Node(NodeType::List);
25✔
118
                x.push_back(Node(Keyword::Begin));
25✔
119
            }
119✔
120
            else
121
            {
122
                for (std::size_t i = 0; i < x.constList().size(); ++i)
131,753✔
123
                {
124
                    auto [node, is_import] = findAndReplaceImports(x.constList()[i]);
115,129✔
125
                    x.list()[i] = node;
115,129✔
126
                }
115,129✔
127
            }
128
        }
16,649✔
129

130
        return std::make_pair(x, /* is_import= */ false);
115,369✔
131
    }
115,463✔
132

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

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

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

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

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

166
            return {};
1✔
167
        }
1✔
168

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

178
        m_logger.traceEnd();
93✔
179

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

190
    std::optional<std::filesystem::path> testExtensions(const std::filesystem::path& folder, const std::string& package_path)
154✔
191
    {
154✔
192
        if (auto code_path = folder / (package_path + ".ark"); std::filesystem::exists(code_path))
247✔
193
            return code_path;
93✔
194
        if (auto module_path = folder / (package_path + ".arkm"); std::filesystem::exists(module_path))
62✔
195
            return module_path;
1✔
196
        return {};
60✔
197
    }
154✔
198

199
    std::filesystem::path ImportSolver::findFile(const std::filesystem::path& file, const Import& import) const
95✔
200
    {
95✔
201
        const std::string package_path = import.packageToPath();
95✔
202
        if (auto maybe_path = testExtensions(m_root, package_path); maybe_path.has_value())
131✔
203
            return maybe_path.value();
36✔
204

205
        // search in all folders in environment path
206
        for (const auto& path : m_libenv)
60✔
207
        {
208
            if (auto maybe_path = testExtensions(path, package_path); maybe_path.has_value())
117✔
209
                return maybe_path.value();
58✔
210
        }
59✔
211

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