ArkScript
A small, lisp-inspired, functional scripting language
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
10#include <Ark/VM/VM.hpp>
11
12namespace Ark::types
13{
14 std::string typeListToString(const std::vector<ValueType>& types)
15 {
16 if (types.size() == 1 && types[0] == ValueType::Any)
17 return "any";
18
19 std::string acc;
20
21 for (std::size_t i = 0, end = types.size(); i < end; ++i)
22 {
23 if (i > 0)
24 acc += ", ";
25 if (i + 1 == end && types.size() > 1)
26 acc += "or ";
27 acc += std::to_string(types[i]);
28 }
29 return acc;
30 }
31
32 void displayContract(const std::string_view& funcname, const Contract& contract, const std::vector<Value>& args, VM& vm, std::ostream& os, const bool colorize)
33 {
34 const std::string checkmark = "✓";
35 const std::string crossmark = "×";
36
37 auto displayArg = [colorize, &os](const Typedef& td, const bool correct) {
38 const std::string arg_str = typeListToString(td.types);
39
40 fmt::dynamic_format_arg_store<fmt::format_context> store;
41 store.push_back(td.variadic ? "variadic " : "");
42 if (colorize)
43 store.push_back(
44 fmt::styled(
45 td.name,
46 correct
47 ? fmt::fg(fmt::color::green)
48 : fmt::fg(fmt::color::magenta)));
49 else
50 store.push_back(td.name);
51 store.push_back(arg_str);
52
53 fmt::vprint(os, " → {}`{}' (expected {})", store);
54 };
55
56 fmt::print(os, "Signature\n ↳ ({}", funcname);
57 for (const Typedef& td : contract.arguments)
58 fmt::print(os, " {}", td.name);
59 fmt::print(os, ")\nArguments\n");
60
61 for (std::size_t i = 0, end = contract.arguments.size(); i < end; ++i)
62 {
63 const Typedef& td = contract.arguments[i];
64
65 if (td.variadic && i < args.size())
66 {
67 // variadic argument in contract and enough provided arguments
68 std::size_t bad_type_count = 0;
69 std::string formatted_varargs;
70 for (std::size_t j = i, args_end = args.size(); j < args_end; ++j)
71 {
72 bool type_ok = true;
73 if (td.types[0] != ValueType::Any && std::ranges::find(td.types, args[j].valueType()) == td.types.end())
74 {
75 bad_type_count++;
76 type_ok = false;
77 }
78 formatted_varargs += fmt::format(
79 "\n {} ({}) {}",
80 args[j].toString(vm, /* show_as_code= */ true),
81 std::to_string(args[j].valueType()),
82 type_ok ? checkmark : crossmark);
83 }
84
85 if (bad_type_count)
86 {
87 displayArg(td, /* correct= */ false);
88
89 fmt::dynamic_format_arg_store<fmt::format_context> store;
90 if (colorize)
91 store.push_back(fmt::styled(bad_type_count, fmt::fg(fmt::color::red)));
92 else
93 store.push_back(bad_type_count);
94 store.push_back(bad_type_count > 1 ? "s" : "");
95 store.push_back(formatted_varargs);
96
97 fmt::vprint(os, ": {} argument{} do not match:{}", store);
98 }
99 else
100 displayArg(td, /* correct= */ true);
101 }
102 else
103 {
104 // provided argument but wrong type
105 if (i < args.size() && td.types[0] != ValueType::Any && std::ranges::find(td.types, args[i].valueType()) == td.types.end())
106 {
107 displayArg(td, /* correct= */ false);
108 const auto type = std::to_string(args[i].valueType());
109
110 fmt::dynamic_format_arg_store<fmt::format_context> store;
111 store.push_back(args[i].toString(vm, /* show_as_code= */ true));
112 if (colorize)
113 store.push_back(fmt::styled(type, fmt::fg(fmt::color::red)));
114 else
115 store.push_back(type);
116 fmt::vprint(os, ", got {} ({})", store);
117 }
118 // non-provided argument
119 else if (i >= args.size())
120 {
121 displayArg(td, /* correct= */ false);
122 if (colorize)
123 fmt::print(os, "{}", fmt::styled(" was not provided", fmt::fg(fmt::color::red)));
124 else
125 fmt::print(os, " was not provided");
126 }
127 else
128 {
129 displayArg(td, /* correct= */ true);
130 fmt::print(os, " {}", checkmark);
131 }
132 }
133 fmt::print(os, "\n");
134 }
135
136 if (contract.arguments.size() < args.size())
137 {
138 fmt::print(os, " → unexpected additional args: ");
139 for (std::size_t i = contract.arguments.size(), end = args.size(); i < end; ++i)
140 {
141 fmt::print(os, "{} ({})", args[i].toString(vm, /* show_as_code= */ true), std::to_string(args[i].valueType()));
142 if (i + 1 != end)
143 fmt::print(os, ", ");
144 }
145 fmt::print(os, "\n");
146 }
147 }
148
149 void generateError(const std::string_view& funcname, const std::vector<Contract>& contracts, const std::vector<Value>& args, VM& vm, std::ostream& os, bool colorize)
150 {
151 {
152 fmt::dynamic_format_arg_store<fmt::format_context> store;
153 if (colorize)
154 store.push_back(fmt::styled(funcname, fmt::fg(fmt::color::cyan)));
155 else
156 store.push_back(funcname);
157 fmt::vprint(os, "Function {} expected ", store);
158 }
159
160 std::vector<Value> sanitizedArgs;
161 std::ranges::copy_if(args, std::back_inserter(sanitizedArgs), [](const Value& value) -> bool {
162 return value.valueType() != ValueType::Undefined;
163 });
164
165 // get expected arguments count
166 std::size_t min_argc = std::numeric_limits<std::size_t>::max(), max_argc = 0;
167 bool variadic = false;
168 for (const auto& [arguments] : contracts)
169 {
170 if (arguments.size() < min_argc)
171 min_argc = arguments.size();
172 if (arguments.size() > max_argc)
173 max_argc = arguments.size();
174
175 if (!arguments.empty() && arguments.back().variadic)
176 variadic = true;
177 }
178
179 bool correct_argcount = true;
180
181 if (min_argc != max_argc)
182 {
183 fmt::dynamic_format_arg_store<fmt::format_context> store;
184 if (colorize)
185 store.push_back(fmt::styled(min_argc, fmt::fg(fmt::color::yellow)));
186 else
187 store.push_back(min_argc);
188 store.push_back(min_argc > 1 ? "s" : "");
189 if (colorize)
190 store.push_back(fmt::styled(max_argc, fmt::fg(fmt::color::yellow)));
191 else
192 store.push_back(max_argc);
193 store.push_back(max_argc > 1 ? "s" : "");
194
195 fmt::vprint(os, "between {} argument{} and {} argument{}", store);
196
197 if (sanitizedArgs.size() < min_argc || sanitizedArgs.size() > max_argc)
198 correct_argcount = false;
199 }
200 else
201 {
202 fmt::dynamic_format_arg_store<fmt::format_context> store;
203 store.push_back(variadic ? "at least " : "");
204 if (colorize)
205 store.push_back(fmt::styled(min_argc, fmt::fg(fmt::color::yellow)));
206 else
207 store.push_back(min_argc);
208 store.push_back(min_argc > 1 ? "s" : "");
209
210 fmt::vprint(os, "{}{} argument{}", store);
211
212 if (sanitizedArgs.size() != min_argc)
213 correct_argcount = false;
214 }
215
216 if (!correct_argcount || variadic)
217 {
218 std::string preposition = (variadic && args.size() >= min_argc) ? "and" : "but";
219 if (colorize)
220 fmt::print(os, " {} got {}", preposition, fmt::styled(sanitizedArgs.size(), fmt::fg(fmt::color::red)));
221 else
222 fmt::print(os, " {} got {}", preposition, sanitizedArgs.size());
223 }
224
225 fmt::print(os, "\nCall\n ↳ ({}", funcname);
226 for (const Value& arg : args)
227 fmt::print(os, " {}", arg.toString(vm, /* show_as_code= */ true));
228 fmt::print(os, ")\n");
229
230 displayContract(funcname, contracts[0], sanitizedArgs, vm, os, colorize);
231 for (std::size_t i = 1, end = contracts.size(); i < end; ++i)
232 {
233 fmt::print(os, "\nAlternative {}:\n", i + 1);
234 displayContract(funcname, contracts[i], sanitizedArgs, vm, os, colorize);
235 }
236 }
237}
The ArkScript virtual machine.
The ArkScript virtual machine, executing ArkScript bytecode.
Definition VM.hpp:47
ValueType valueType() const noexcept
Definition Value.hpp:151
ARK_API void generateError(const std::string_view &funcname, const std::vector< Contract > &contracts, const std::vector< Value > &args, VM &vm, std::ostream &os=std::cout, bool colorize=true)
Generate an error message based on a given set of types contracts provided argument list.
void displayContract(const std::string_view &funcname, const Contract &contract, const std::vector< Value > &args, VM &vm, std::ostream &os, const bool colorize)
std::string typeListToString(const std::vector< ValueType > &types)
@ Any
Used only for typechecking.
std::string to_string(const Ark::ValueType type) noexcept
Definition Value.hpp:258
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