ArkScript
A small, fast, functional and scripting language for video games
TypeChecker.cpp
Go to the documentation of this file.
1#include <Ark/TypeChecker.hpp>
2
3#include <limits>
4#include <algorithm>
5#include <fmt/core.h>
6#include <fmt/args.h>
7#include <fmt/color.h>
8#include <fmt/ostream.h>
9
10namespace Ark::types
11{
12 std::string typeListToString(const std::vector<ValueType>& types)
13 {
14 if (types.size() == 1 && types[0] == ValueType::Any)
15 return "any";
16
17 std::string acc;
18
19 for (std::size_t i = 0, end = types.size(); i < end; ++i)
20 {
21 if (i > 0)
22 acc += ", ";
23 acc += types_to_str[static_cast<std::size_t>(types[i])];
24 }
25 return acc;
26 }
27
28 void displayContract(const Contract& contract, const std::vector<Value>& args, std::ostream& os, const bool colorize)
29 {
30 auto displayArg = [colorize, &os](const Typedef& td, const bool correct) {
31 const std::string arg_str = typeListToString(td.types);
32
33 fmt::dynamic_format_arg_store<fmt::format_context> store;
34 store.push_back(td.variadic ? "variadic " : "");
35 if (colorize)
36 store.push_back(
37 fmt::styled(
38 td.name,
39 correct
40 ? fmt::fg(fmt::color::green)
41 : fmt::fg(fmt::color::magenta)));
42 else
43 store.push_back(td.name);
44 store.push_back(arg_str);
45
46 fmt::vprint(os, " -> {}{} ({})", store);
47 };
48
49 for (std::size_t i = 0, end = contract.arguments.size(); i < end; ++i)
50 {
51 const Typedef& td = contract.arguments[i];
52
53 if (td.variadic && i < args.size())
54 {
55 // variadic argument in contract and enough provided arguments
56 std::size_t bad_type = 0;
57 for (std::size_t j = i, args_end = args.size(); j < args_end; ++j)
58 {
59 if (td.types[0] != ValueType::Any && std::ranges::find(td.types, args[j].valueType()) == td.types.end())
60 bad_type++;
61 }
62
63 if (bad_type)
64 {
65 displayArg(td, /* correct= */ false);
66
67 fmt::dynamic_format_arg_store<fmt::format_context> store;
68 if (colorize)
69 store.push_back(fmt::styled(bad_type, fmt::fg(fmt::color::red)));
70 else
71 store.push_back(bad_type);
72 store.push_back(bad_type > 1 ? "s" : "");
73
74 fmt::vprint(os, " {} argument{} do not match", store);
75 }
76 else
77 displayArg(td, /* correct= */ true);
78 }
79 else
80 {
81 // provided argument but wrong type
82 if (i < args.size() && td.types[0] != ValueType::Any && std::ranges::find(td.types, args[i].valueType()) == td.types.end())
83 {
84 displayArg(td, /* correct= */ false);
85 const auto type = types_to_str[static_cast<std::size_t>(args[i].valueType())];
86
87 fmt::dynamic_format_arg_store<fmt::format_context> store;
88 if (colorize)
89 store.push_back(fmt::styled(type, fmt::fg(fmt::color::red)));
90 else
91 store.push_back(type);
92 fmt::vprint(os, " was of type {}", store);
93 }
94 // non-provided argument
95 else if (i >= args.size())
96 {
97 displayArg(td, /* correct= */ false);
98 if (colorize)
99 fmt::print(os, "{}", fmt::styled(" was not provided", fmt::fg(fmt::color::red)));
100 else
101 fmt::print(os, " was not provided");
102 }
103 else
104 displayArg(td, /* correct= */ true);
105 }
106 fmt::print(os, "\n");
107 }
108 }
109
110 void generateError(const std::string_view& funcname, const std::vector<Contract>& contracts, const std::vector<Value>& args, std::ostream& os, bool colorize)
111 {
112 {
113 fmt::dynamic_format_arg_store<fmt::format_context> store;
114 if (colorize)
115 store.push_back(fmt::styled(funcname, fmt::fg(fmt::color::cyan)));
116 else
117 store.push_back(funcname);
118 fmt::vprint(os, "Function {} expected ", store);
119 }
120
121 std::vector<Value> sanitizedArgs;
122 std::ranges::copy_if(args, std::back_inserter(sanitizedArgs), [](const Value& value) -> bool {
123 return value.valueType() != ValueType::Undefined;
124 });
125
126 // get expected arguments count
127 std::size_t min_argc = std::numeric_limits<std::size_t>::max(), max_argc = 0;
128 bool variadic = false;
129 for (const auto& [arguments] : contracts)
130 {
131 if (arguments.size() < min_argc)
132 min_argc = arguments.size();
133 if (arguments.size() > max_argc)
134 max_argc = arguments.size();
135
136 if (!arguments.empty() && arguments.back().variadic)
137 variadic = true;
138 }
139
140 bool correct_argcount = true;
141
142 if (min_argc != max_argc)
143 {
144 fmt::dynamic_format_arg_store<fmt::format_context> store;
145 if (colorize)
146 store.push_back(fmt::styled(min_argc, fmt::fg(fmt::color::yellow)));
147 else
148 store.push_back(min_argc);
149 store.push_back(min_argc > 1 ? "s" : "");
150 if (colorize)
151 store.push_back(fmt::styled(max_argc, fmt::fg(fmt::color::yellow)));
152 else
153 store.push_back(max_argc);
154 store.push_back(max_argc > 1 ? "s" : "");
155
156 fmt::vprint(os, "between {} argument{} and {} argument{}", store);
157
158 if (sanitizedArgs.size() < min_argc || sanitizedArgs.size() > max_argc)
159 correct_argcount = false;
160 }
161 else
162 {
163 fmt::dynamic_format_arg_store<fmt::format_context> store;
164 store.push_back(variadic ? "at least " : "");
165 if (colorize)
166 store.push_back(fmt::styled(min_argc, fmt::fg(fmt::color::yellow)));
167 else
168 store.push_back(min_argc);
169 store.push_back(min_argc > 1 ? "s" : "");
170
171 fmt::vprint(os, "{}{} argument{}", store);
172
173 if (sanitizedArgs.size() != min_argc)
174 correct_argcount = false;
175 }
176
177 if (!correct_argcount || variadic)
178 {
179 std::string preposition = (variadic && args.size() >= min_argc) ? "and" : "but";
180 if (colorize)
181 fmt::print(os, " {} got {}", preposition, fmt::styled(sanitizedArgs.size(), fmt::fg(fmt::color::red)));
182 else
183 fmt::print(os, " {} got {}", preposition, sanitizedArgs.size());
184 }
185
186 fmt::print(os, "\n");
187
188 displayContract(contracts[0], sanitizedArgs, os, colorize);
189 for (std::size_t i = 1, end = contracts.size(); i < end; ++i)
190 {
191 fmt::print(os, "Alternative {}:\n", i + 1);
192 displayContract(contracts[i], sanitizedArgs, os, colorize);
193 }
194 }
195}
ValueType valueType() const noexcept
Definition Value.hpp:113
ARK_API void generateError(const std::string_view &funcname, const std::vector< Contract > &contracts, const std::vector< Value > &args, std::ostream &os=std::cout, bool colorize=true)
Generate an error message based on a given set of types contracts provided argument list.
std::string typeListToString(const std::vector< ValueType > &types)
void displayContract(const Contract &contract, const std::vector< Value > &args, std::ostream &os, const bool colorize)
constexpr std::array types_to_str
Definition Value.hpp:51
@ Any
Used only for typechecking.
A contract is a list of typed arguments that a function can follow.
std::vector< Typedef > arguments
A type definition within a contract.
std::vector< ValueType > types