Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/modules/gdscript/tests/test_gdscript.cpp
10278 views
1
/**************************************************************************/
2
/* test_gdscript.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#include "test_gdscript.h"
32
33
#include "../gdscript_analyzer.h"
34
#include "../gdscript_compiler.h"
35
#include "../gdscript_parser.h"
36
#include "../gdscript_tokenizer.h"
37
#include "../gdscript_tokenizer_buffer.h"
38
39
#include "core/config/project_settings.h"
40
#include "core/io/file_access.h"
41
#include "core/os/os.h"
42
#include "core/string/string_builder.h"
43
44
#ifdef TOOLS_ENABLED
45
#include "editor/settings/editor_settings.h"
46
#endif
47
48
namespace GDScriptTests {
49
50
static void test_tokenizer(const String &p_code, const Vector<String> &p_lines) {
51
GDScriptTokenizerText tokenizer;
52
tokenizer.set_source_code(p_code);
53
54
int tab_size = 4;
55
#ifdef TOOLS_ENABLED
56
if (EditorSettings::get_singleton()) {
57
tab_size = EditorSettings::get_singleton()->get_setting("text_editor/behavior/indent/size");
58
}
59
#endif // TOOLS_ENABLED
60
String tab = String(" ").repeat(tab_size);
61
62
GDScriptTokenizer::Token current = tokenizer.scan();
63
while (current.type != GDScriptTokenizer::Token::TK_EOF) {
64
StringBuilder token;
65
token += " --> "; // Padding for line number.
66
67
if (current.start_line != current.end_line) {
68
// Print "vvvvvv" to point at the token.
69
StringBuilder pointer;
70
pointer += " "; // Padding for line number.
71
72
int line_width = 0;
73
if (current.start_line - 1 >= 0 && current.start_line - 1 < p_lines.size()) {
74
line_width = p_lines[current.start_line - 1].replace("\t", tab).length();
75
}
76
77
const int offset = MAX(0, current.start_column - 1);
78
const int width = MAX(0, line_width - current.start_column + 1);
79
pointer += String::chr(' ').repeat(offset) + String::chr('v').repeat(width);
80
81
print_line(pointer.as_string());
82
}
83
84
for (int l = current.start_line; l <= current.end_line && l <= p_lines.size(); l++) {
85
print_line(vformat("%04d %s", l, p_lines[l - 1]).replace("\t", tab));
86
}
87
88
{
89
// Print "^^^^^^" to point at the token.
90
StringBuilder pointer;
91
pointer += " "; // Padding for line number.
92
93
if (current.start_line == current.end_line) {
94
const int offset = MAX(0, current.start_column - 1);
95
const int width = MAX(0, current.end_column - current.start_column);
96
pointer += String::chr(' ').repeat(offset) + String::chr('^').repeat(width);
97
} else {
98
const int width = MAX(0, current.end_column - 1);
99
pointer += String::chr('^').repeat(width);
100
}
101
102
print_line(pointer.as_string());
103
}
104
105
token += current.get_name();
106
107
if (current.type == GDScriptTokenizer::Token::ERROR || current.type == GDScriptTokenizer::Token::LITERAL || current.type == GDScriptTokenizer::Token::IDENTIFIER || current.type == GDScriptTokenizer::Token::ANNOTATION) {
108
token += "(";
109
token += Variant::get_type_name(current.literal.get_type());
110
token += ") ";
111
token += current.literal;
112
}
113
114
print_line(token.as_string());
115
116
print_line("-------------------------------------------------------");
117
118
current = tokenizer.scan();
119
}
120
121
print_line(current.get_name()); // Should be EOF
122
}
123
124
static void test_tokenizer_buffer(const Vector<uint8_t> &p_buffer, const Vector<String> &p_lines);
125
126
static void test_tokenizer_buffer(const String &p_code, const Vector<String> &p_lines) {
127
Vector<uint8_t> binary = GDScriptTokenizerBuffer::parse_code_string(p_code, GDScriptTokenizerBuffer::COMPRESS_NONE);
128
test_tokenizer_buffer(binary, p_lines);
129
}
130
131
static void test_tokenizer_buffer(const Vector<uint8_t> &p_buffer, const Vector<String> &p_lines) {
132
GDScriptTokenizerBuffer tokenizer;
133
tokenizer.set_code_buffer(p_buffer);
134
135
int tab_size = 4;
136
#ifdef TOOLS_ENABLED
137
if (EditorSettings::get_singleton()) {
138
tab_size = EditorSettings::get_singleton()->get_setting("text_editor/behavior/indent/size");
139
}
140
#endif // TOOLS_ENABLED
141
String tab = String(" ").repeat(tab_size);
142
143
GDScriptTokenizer::Token current = tokenizer.scan();
144
while (current.type != GDScriptTokenizer::Token::TK_EOF) {
145
StringBuilder token;
146
token += " --> "; // Padding for line number.
147
148
for (int l = current.start_line; l <= current.end_line && l <= p_lines.size(); l++) {
149
print_line(vformat("%04d %s", l, p_lines[l - 1]).replace("\t", tab));
150
}
151
152
token += current.get_name();
153
154
if (current.type == GDScriptTokenizer::Token::ERROR || current.type == GDScriptTokenizer::Token::LITERAL || current.type == GDScriptTokenizer::Token::IDENTIFIER || current.type == GDScriptTokenizer::Token::ANNOTATION) {
155
token += "(";
156
token += Variant::get_type_name(current.literal.get_type());
157
token += ") ";
158
token += current.literal;
159
}
160
161
print_line(token.as_string());
162
163
print_line("-------------------------------------------------------");
164
165
current = tokenizer.scan();
166
}
167
168
print_line(current.get_name()); // Should be EOF
169
}
170
171
static void test_parser(const String &p_code, const String &p_script_path, const Vector<String> &p_lines) {
172
GDScriptParser parser;
173
Error err = parser.parse(p_code, p_script_path, false);
174
175
if (err != OK) {
176
const List<GDScriptParser::ParserError> &errors = parser.get_errors();
177
for (const GDScriptParser::ParserError &error : errors) {
178
print_line(vformat("%02d:%02d: %s", error.line, error.column, error.message));
179
}
180
}
181
182
GDScriptAnalyzer analyzer(&parser);
183
err = analyzer.analyze();
184
185
if (err != OK) {
186
const List<GDScriptParser::ParserError> &errors = parser.get_errors();
187
for (const GDScriptParser::ParserError &error : errors) {
188
print_line(vformat("%02d:%02d: %s", error.line, error.column, error.message));
189
}
190
}
191
192
#ifdef TOOLS_ENABLED
193
GDScriptParser::TreePrinter printer;
194
printer.print_tree(parser);
195
#endif
196
}
197
198
static void disassemble_function(const GDScriptFunction *p_func, const Vector<String> &p_lines) {
199
ERR_FAIL_NULL(p_func);
200
201
String arg_string;
202
bool is_first_arg = true;
203
for (const PropertyInfo &arg_info : p_func->get_method_info().arguments) {
204
if (!is_first_arg) {
205
arg_string += ", ";
206
}
207
arg_string += arg_info.name;
208
is_first_arg = false;
209
}
210
if (p_func->is_vararg()) {
211
// `MethodInfo` does not support the rest parameter name.
212
arg_string += (p_func->get_argument_count() == 0) ? "...args" : ", ...args";
213
}
214
215
print_line(vformat("Function %s(%s)", p_func->get_name(), arg_string));
216
#ifdef TOOLS_ENABLED
217
p_func->disassemble(p_lines);
218
#endif
219
print_line("");
220
print_line("");
221
}
222
223
static void recursively_disassemble_functions(const Ref<GDScript> p_script, const Vector<String> &p_lines) {
224
print_line(vformat("Class %s", p_script->get_fully_qualified_name()));
225
print_line("");
226
print_line("");
227
228
const GDScriptFunction *implicit_initializer = p_script->get_implicit_initializer();
229
if (implicit_initializer != nullptr) {
230
disassemble_function(implicit_initializer, p_lines);
231
}
232
233
const GDScriptFunction *implicit_ready = p_script->get_implicit_ready();
234
if (implicit_ready != nullptr) {
235
disassemble_function(implicit_ready, p_lines);
236
}
237
238
const GDScriptFunction *static_initializer = p_script->get_static_initializer();
239
if (static_initializer != nullptr) {
240
disassemble_function(static_initializer, p_lines);
241
}
242
243
for (const KeyValue<GDScriptFunction *, GDScript::LambdaInfo> &E : p_script->get_lambda_info()) {
244
disassemble_function(E.key, p_lines);
245
}
246
247
for (const KeyValue<StringName, GDScriptFunction *> &E : p_script->get_member_functions()) {
248
disassemble_function(E.value, p_lines);
249
}
250
251
for (const KeyValue<StringName, Ref<GDScript>> &E : p_script->get_subclasses()) {
252
recursively_disassemble_functions(E.value, p_lines);
253
}
254
}
255
256
static void test_compiler(const String &p_code, const String &p_script_path, const Vector<String> &p_lines) {
257
GDScriptParser parser;
258
Error err = parser.parse(p_code, p_script_path, false);
259
260
if (err != OK) {
261
print_line("Error in parser:");
262
const List<GDScriptParser::ParserError> &errors = parser.get_errors();
263
for (const GDScriptParser::ParserError &error : errors) {
264
print_line(vformat("%02d:%02d: %s", error.line, error.column, error.message));
265
}
266
return;
267
}
268
269
GDScriptAnalyzer analyzer(&parser);
270
err = analyzer.analyze();
271
272
if (err != OK) {
273
print_line("Error in analyzer:");
274
const List<GDScriptParser::ParserError> &errors = parser.get_errors();
275
for (const GDScriptParser::ParserError &error : errors) {
276
print_line(vformat("%02d:%02d: %s", error.line, error.column, error.message));
277
}
278
return;
279
}
280
281
GDScriptCompiler compiler;
282
Ref<GDScript> script;
283
script.instantiate();
284
script->set_path(p_script_path);
285
286
err = compiler.compile(&parser, script.ptr(), false);
287
288
if (err) {
289
print_line("Error in compiler:");
290
print_line(vformat("%02d:%02d: %s", compiler.get_error_line(), compiler.get_error_column(), compiler.get_error()));
291
return;
292
}
293
294
recursively_disassemble_functions(script, p_lines);
295
}
296
297
void test(TestType p_type) {
298
List<String> cmdlargs = OS::get_singleton()->get_cmdline_args();
299
300
if (cmdlargs.is_empty()) {
301
return;
302
}
303
304
String test = cmdlargs.back()->get();
305
if (!test.ends_with(".gd") && !test.ends_with(".gdc")) {
306
print_line("This test expects a path to a GDScript file as its last parameter. Got: " + test);
307
return;
308
}
309
310
Ref<FileAccess> fa = FileAccess::open(test, FileAccess::READ);
311
ERR_FAIL_COND_MSG(fa.is_null(), "Could not open file: " + test);
312
313
// Initialize the language for the test routine.
314
init_language(fa->get_path_absolute().get_base_dir());
315
316
// Load global classes.
317
TypedArray<Dictionary> script_classes = ProjectSettings::get_singleton()->get_global_class_list();
318
for (int i = 0; i < script_classes.size(); i++) {
319
Dictionary c = script_classes[i];
320
if (!c.has("class") || !c.has("language") || !c.has("path") || !c.has("base") || !c.has("is_abstract") || !c.has("is_tool")) {
321
continue;
322
}
323
ScriptServer::add_global_class(c["class"], c["base"], c["language"], c["path"], c["is_abstract"], c["is_tool"]);
324
}
325
326
Vector<uint8_t> buf;
327
uint64_t flen = fa->get_length();
328
buf.resize(flen + 1);
329
fa->get_buffer(buf.ptrw(), flen);
330
buf.write[flen] = 0;
331
332
String code = String::utf8((const char *)&buf[0]);
333
334
Vector<String> lines;
335
int last = 0;
336
for (int i = 0; i <= code.length(); i++) {
337
if (code[i] == '\n' || code[i] == 0) {
338
lines.push_back(code.substr(last, i - last));
339
last = i + 1;
340
}
341
}
342
343
switch (p_type) {
344
case TEST_TOKENIZER:
345
test_tokenizer(code, lines);
346
break;
347
case TEST_TOKENIZER_BUFFER:
348
if (test.ends_with(".gdc")) {
349
test_tokenizer_buffer(buf, lines);
350
} else {
351
test_tokenizer_buffer(code, lines);
352
}
353
break;
354
case TEST_PARSER:
355
test_parser(code, test, lines);
356
break;
357
case TEST_COMPILER:
358
test_compiler(code, test, lines);
359
break;
360
case TEST_BYTECODE:
361
print_line("Not implemented.");
362
}
363
364
finish_language();
365
}
366
} // namespace GDScriptTests
367
368