Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/CLI/src/Repl.cpp
2725 views
1
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
2
#include "Luau/Repl.h"
3
4
#include "Luau/Common.h"
5
#include "lua.h"
6
#include "lualib.h"
7
8
#include "Luau/CodeGen.h"
9
#include "Luau/Compiler.h"
10
#include "Luau/Parser.h"
11
#include "Luau/TimeTrace.h"
12
#include "Luau/Counters.h"
13
#include "Luau/Coverage.h"
14
#include "Luau/FileUtils.h"
15
#include "Luau/Flags.h"
16
#include "Luau/Profiler.h"
17
#include "Luau/ReplRequirer.h"
18
#include "Luau/Require.h"
19
20
#include "isocline.h"
21
22
#include <memory>
23
#include <string>
24
#include <string_view>
25
26
#ifdef _WIN32
27
#include <io.h>
28
#include <fcntl.h>
29
30
#define WIN32_LEAN_AND_MEAN
31
#include <windows.h>
32
#endif
33
34
#ifdef __linux__
35
#include <unistd.h>
36
#endif
37
38
#ifdef CALLGRIND
39
#include <valgrind/callgrind.h>
40
#endif
41
42
#include <locale.h>
43
#include <signal.h>
44
45
LUAU_FASTFLAG(DebugLuauTimeTracing)
46
47
constexpr int MaxTraversalLimit = 50;
48
49
static bool codegen = false;
50
static int program_argc = 0;
51
char** program_argv = nullptr;
52
53
// Ctrl-C handling
54
static void sigintCallback(lua_State* L, int gc)
55
{
56
if (gc >= 0)
57
return;
58
59
lua_callbacks(L)->interrupt = NULL;
60
61
lua_rawcheckstack(L, 1); // reserve space for error string
62
luaL_error(L, "Execution interrupted");
63
}
64
65
static lua_State* replState = NULL;
66
67
#ifdef _WIN32
68
BOOL WINAPI sigintHandler(DWORD signal)
69
{
70
if (signal == CTRL_C_EVENT && replState)
71
lua_callbacks(replState)->interrupt = &sigintCallback;
72
return TRUE;
73
}
74
#else
75
static void sigintHandler(int signum)
76
{
77
if (signum == SIGINT && replState)
78
lua_callbacks(replState)->interrupt = &sigintCallback;
79
}
80
#endif
81
82
struct GlobalOptions
83
{
84
int optimizationLevel = 1;
85
int debugLevel = 1;
86
} globalOptions;
87
88
static Luau::CompileOptions copts()
89
{
90
Luau::CompileOptions result = {};
91
result.optimizationLevel = globalOptions.optimizationLevel;
92
result.debugLevel = globalOptions.debugLevel;
93
result.typeInfoLevel = 1;
94
result.coverageLevel = coverageActive() ? 2 : 0;
95
96
return result;
97
}
98
99
static int lua_loadstring(lua_State* L)
100
{
101
size_t l = 0;
102
const char* s = luaL_checklstring(L, 1, &l);
103
const char* chunkname = luaL_optstring(L, 2, s);
104
105
lua_setsafeenv(L, LUA_ENVIRONINDEX, false);
106
107
std::string bytecode = Luau::compile(std::string(s, l), copts());
108
if (luau_load(L, chunkname, bytecode.data(), bytecode.size(), 0) == 0)
109
return 1;
110
111
lua_pushnil(L);
112
lua_insert(L, -2); // put before error message
113
return 2; // return nil plus error message
114
}
115
116
static int lua_collectgarbage(lua_State* L)
117
{
118
const char* option = luaL_optstring(L, 1, "collect");
119
120
if (strcmp(option, "collect") == 0)
121
{
122
lua_gc(L, LUA_GCCOLLECT, 0);
123
return 0;
124
}
125
126
if (strcmp(option, "count") == 0)
127
{
128
int c = lua_gc(L, LUA_GCCOUNT, 0);
129
lua_pushnumber(L, c);
130
return 1;
131
}
132
133
luaL_error(L, "collectgarbage must be called with 'count' or 'collect'");
134
}
135
136
#ifdef CALLGRIND
137
static int lua_callgrind(lua_State* L)
138
{
139
const char* option = luaL_checkstring(L, 1);
140
141
if (strcmp(option, "running") == 0)
142
{
143
int r = RUNNING_ON_VALGRIND;
144
lua_pushboolean(L, r);
145
return 1;
146
}
147
148
if (strcmp(option, "zero") == 0)
149
{
150
CALLGRIND_ZERO_STATS;
151
return 0;
152
}
153
154
if (strcmp(option, "dump") == 0)
155
{
156
const char* name = luaL_checkstring(L, 2);
157
158
CALLGRIND_DUMP_STATS_AT(name);
159
return 0;
160
}
161
162
luaL_error(L, "callgrind must be called with one of 'running', 'zero', 'dump'");
163
}
164
#endif
165
166
void* createCliRequireContext(lua_State* L)
167
{
168
void* ctx = lua_newuserdatadtor(
169
L,
170
sizeof(ReplRequirer),
171
[](void* ptr)
172
{
173
static_cast<ReplRequirer*>(ptr)->~ReplRequirer();
174
}
175
);
176
177
if (!ctx)
178
luaL_error(L, "unable to allocate ReplRequirer");
179
180
ctx = new (ctx) ReplRequirer{
181
copts,
182
coverageActive,
183
[]()
184
{
185
return codegen;
186
},
187
coverageTrack,
188
countersActive,
189
countersTrack
190
};
191
192
// Store ReplRequirer in the registry to keep it alive for the lifetime of
193
// this lua_State. Memory address is used as a key to avoid collisions.
194
lua_pushlightuserdata(L, ctx);
195
lua_insert(L, -2);
196
lua_settable(L, LUA_REGISTRYINDEX);
197
198
return ctx;
199
}
200
201
void setupState(lua_State* L)
202
{
203
if (codegen)
204
Luau::CodeGen::create(L);
205
206
luaL_openlibs(L);
207
208
static const luaL_Reg funcs[] = {
209
{"loadstring", lua_loadstring},
210
{"collectgarbage", lua_collectgarbage},
211
#ifdef CALLGRIND
212
{"callgrind", lua_callgrind},
213
#endif
214
{NULL, NULL},
215
};
216
217
lua_pushvalue(L, LUA_GLOBALSINDEX);
218
luaL_register(L, NULL, funcs);
219
lua_pop(L, 1);
220
221
luaopen_require(L, requireConfigInit, createCliRequireContext(L));
222
223
luaL_sandbox(L);
224
}
225
226
void setupArguments(lua_State* L, int argc, char** argv)
227
{
228
lua_checkstack(L, argc);
229
230
for (int i = 0; i < argc; ++i)
231
lua_pushstring(L, argv[i]);
232
}
233
234
std::string runCode(lua_State* L, const std::string& source)
235
{
236
lua_checkstack(L, LUA_MINSTACK);
237
238
std::string bytecode = Luau::compile(source, copts());
239
240
if (luau_load(L, "=stdin", bytecode.data(), bytecode.size(), 0) != 0)
241
{
242
size_t len;
243
const char* msg = lua_tolstring(L, -1, &len);
244
245
std::string error(msg, len);
246
lua_pop(L, 1);
247
248
return error;
249
}
250
251
lua_State* T = lua_newthread(L);
252
253
lua_pushvalue(L, -2);
254
lua_remove(L, -3);
255
lua_xmove(L, T, 1);
256
257
int status = lua_resume(T, NULL, 0);
258
259
if (status == 0)
260
{
261
int n = lua_gettop(T);
262
263
if (n)
264
{
265
luaL_checkstack(T, LUA_MINSTACK, "too many results to print");
266
lua_getglobal(T, "_PRETTYPRINT");
267
// If _PRETTYPRINT is nil, then use the standard print function instead
268
if (lua_isnil(T, -1))
269
{
270
lua_pop(T, 1);
271
lua_getglobal(T, "print");
272
}
273
lua_insert(T, 1);
274
lua_pcall(T, n, 0, 0);
275
}
276
277
lua_pop(L, 1);
278
return std::string();
279
}
280
else
281
{
282
std::string error;
283
284
if (status == LUA_YIELD)
285
{
286
error = "thread yielded unexpectedly";
287
}
288
else if (const char* str = lua_tostring(T, -1))
289
{
290
error = str;
291
}
292
293
error += "\nstack backtrace:\n";
294
error += lua_debugtrace(T);
295
296
lua_pop(L, 1);
297
return error;
298
}
299
}
300
301
// Replaces the top of the lua stack with the metatable __index for the value
302
// if it exists. Returns true iff __index exists.
303
static bool tryReplaceTopWithIndex(lua_State* L)
304
{
305
if (luaL_getmetafield(L, -1, "__index"))
306
{
307
// Remove the table leaving __index on the top of stack
308
lua_remove(L, -2);
309
return true;
310
}
311
return false;
312
}
313
314
315
// This function is similar to lua_gettable, but it avoids calling any
316
// lua callback functions (e.g. __index) which might modify the Lua VM state.
317
static void safeGetTable(lua_State* L, int tableIndex)
318
{
319
lua_pushvalue(L, tableIndex); // Duplicate the table
320
321
// The loop invariant is that the table to search is at -1
322
// and the key is at -2.
323
for (int loopCount = 0;; loopCount++)
324
{
325
lua_pushvalue(L, -2); // Duplicate the key
326
lua_rawget(L, -2); // Try to find the key
327
if (!lua_isnil(L, -1) || loopCount >= MaxTraversalLimit)
328
{
329
// Either the key has been found, and/or we have reached the max traversal limit
330
break;
331
}
332
else
333
{
334
lua_pop(L, 1); // Pop the nil result
335
if (!luaL_getmetafield(L, -1, "__index"))
336
{
337
lua_pushnil(L);
338
break;
339
}
340
else if (lua_istable(L, -1))
341
{
342
// Replace the current table being searched with __index table
343
lua_replace(L, -2);
344
}
345
else
346
{
347
lua_pop(L, 1); // Pop the value
348
lua_pushnil(L);
349
break;
350
}
351
}
352
}
353
354
lua_remove(L, -2); // Remove the table
355
lua_remove(L, -2); // Remove the original key
356
}
357
358
// completePartialMatches finds keys that match the specified 'prefix'
359
// Note: the table/object to be searched must be on the top of the Lua stack
360
static void completePartialMatches(
361
lua_State* L,
362
bool completeOnlyFunctions,
363
const std::string& editBuffer,
364
std::string_view prefix,
365
const AddCompletionCallback& addCompletionCallback
366
)
367
{
368
for (int i = 0; i < MaxTraversalLimit && lua_istable(L, -1); i++)
369
{
370
// table, key
371
lua_pushnil(L);
372
373
// Loop over all the keys in the current table
374
while (lua_next(L, -2) != 0)
375
{
376
if (lua_type(L, -2) == LUA_TSTRING)
377
{
378
// table, key, value
379
std::string_view key = lua_tostring(L, -2);
380
int valueType = lua_type(L, -1);
381
382
// If the last separator was a ':' (i.e. a method call) then only functions should be completed.
383
bool requiredValueType = (!completeOnlyFunctions || valueType == LUA_TFUNCTION);
384
385
if (!key.empty() && requiredValueType && Luau::startsWith(key, prefix))
386
{
387
std::string completedComponent(key.substr(prefix.size()));
388
std::string completion(editBuffer + completedComponent);
389
if (valueType == LUA_TFUNCTION)
390
{
391
// Add an opening paren for function calls by default.
392
completion += "(";
393
}
394
addCompletionCallback(completion, std::string(key));
395
}
396
}
397
lua_pop(L, 1);
398
}
399
400
// Replace the current table being searched with an __index table if one exists
401
if (!tryReplaceTopWithIndex(L))
402
{
403
break;
404
}
405
}
406
}
407
408
static void completeIndexer(lua_State* L, const std::string& editBuffer, const AddCompletionCallback& addCompletionCallback)
409
{
410
std::string_view lookup = editBuffer;
411
bool completeOnlyFunctions = false;
412
413
lua_checkstack(L, LUA_MINSTACK);
414
415
// Push the global variable table to begin the search
416
lua_pushvalue(L, LUA_GLOBALSINDEX);
417
418
for (;;)
419
{
420
size_t sep = lookup.find_first_of(".:");
421
std::string_view prefix = lookup.substr(0, sep);
422
423
if (sep == std::string_view::npos)
424
{
425
completePartialMatches(L, completeOnlyFunctions, editBuffer, prefix, addCompletionCallback);
426
break;
427
}
428
else
429
{
430
// find the key in the table
431
lua_pushlstring(L, prefix.data(), prefix.size());
432
safeGetTable(L, -2);
433
lua_remove(L, -2);
434
435
if (lua_istable(L, -1) || tryReplaceTopWithIndex(L))
436
{
437
completeOnlyFunctions = lookup[sep] == ':';
438
lookup.remove_prefix(sep + 1);
439
}
440
else
441
{
442
// Unable to search for keys, so stop searching
443
break;
444
}
445
}
446
}
447
448
lua_pop(L, 1);
449
}
450
451
void getCompletions(lua_State* L, const std::string& editBuffer, const AddCompletionCallback& addCompletionCallback)
452
{
453
completeIndexer(L, editBuffer, addCompletionCallback);
454
}
455
456
static void icGetCompletions(ic_completion_env_t* cenv, const char* editBuffer)
457
{
458
auto* L = reinterpret_cast<lua_State*>(ic_completion_arg(cenv));
459
460
getCompletions(
461
L,
462
std::string(editBuffer),
463
[cenv](const std::string& completion, const std::string& display)
464
{
465
ic_add_completion_ex(cenv, completion.data(), display.data(), nullptr);
466
}
467
);
468
}
469
470
static bool isMethodOrFunctionChar(const char* s, long len)
471
{
472
char c = *s;
473
return len == 1 && (isalnum(c) || c == '.' || c == ':' || c == '_');
474
}
475
476
static void completeRepl(ic_completion_env_t* cenv, const char* editBuffer)
477
{
478
ic_complete_word(cenv, editBuffer, icGetCompletions, isMethodOrFunctionChar);
479
}
480
481
static void loadHistory(const char* name)
482
{
483
std::string path;
484
485
if (const char* home = getenv("HOME"))
486
{
487
path = joinPaths(home, name);
488
}
489
else if (const char* userProfile = getenv("USERPROFILE"))
490
{
491
path = joinPaths(userProfile, name);
492
}
493
494
if (!path.empty())
495
ic_set_history(path.c_str(), -1 /* default entries (= 200) */);
496
}
497
498
static void runReplImpl(lua_State* L)
499
{
500
ic_set_default_completer(completeRepl, L);
501
502
// Reset the locale to C
503
setlocale(LC_ALL, "C");
504
505
// Make brace matching easier to see
506
ic_style_def("ic-bracematch", "teal");
507
508
// Prevent auto insertion of braces
509
ic_enable_brace_insertion(false);
510
511
// Loads history from the given file; isocline automatically saves the history on process exit
512
loadHistory(".luau_history");
513
514
std::string buffer;
515
516
for (;;)
517
{
518
const char* prompt = buffer.empty() ? "" : ">";
519
std::unique_ptr<char, void (*)(void*)> line(ic_readline(prompt), free);
520
if (!line)
521
break;
522
523
if (buffer.empty() && runCode(L, std::string("return ") + line.get()) == std::string())
524
{
525
ic_history_add(line.get());
526
continue;
527
}
528
529
if (!buffer.empty())
530
buffer += "\n";
531
buffer += line.get();
532
533
std::string error = runCode(L, buffer);
534
535
if (error.length() >= 5 && error.compare(error.length() - 5, 5, "<eof>") == 0)
536
{
537
continue;
538
}
539
540
if (error.length())
541
{
542
fprintf(stdout, "%s\n", error.c_str());
543
}
544
545
ic_history_add(buffer.c_str());
546
buffer.clear();
547
}
548
}
549
550
static void runRepl()
551
{
552
std::unique_ptr<lua_State, void (*)(lua_State*)> globalState(luaL_newstate(), lua_close);
553
lua_State* L = globalState.get();
554
555
setupState(L);
556
557
// setup Ctrl+C handling
558
replState = L;
559
#ifdef _WIN32
560
SetConsoleCtrlHandler(sigintHandler, TRUE);
561
#else
562
signal(SIGINT, sigintHandler);
563
#endif
564
565
luaL_sandboxthread(L);
566
runReplImpl(L);
567
}
568
569
// `repl` is used it indicate if a repl should be started after executing the file.
570
static bool runFile(const char* name, lua_State* GL, bool repl)
571
{
572
std::optional<std::string> source = readFile(name);
573
if (!source)
574
{
575
fprintf(stderr, "Error opening %s\n", name);
576
return false;
577
}
578
579
// module needs to run in a new thread, isolated from the rest
580
lua_State* L = lua_newthread(GL);
581
582
// new thread needs to have the globals sandboxed
583
luaL_sandboxthread(L);
584
585
std::string chunkname = "@" + normalizePath(name);
586
587
std::string bytecode = Luau::compile(*source, copts());
588
int status = 0;
589
590
if (luau_load(L, chunkname.c_str(), bytecode.data(), bytecode.size(), 0) == 0)
591
{
592
if (codegen)
593
{
594
Luau::CodeGen::CompilationOptions nativeOptions;
595
596
if (countersActive())
597
nativeOptions.recordCounters = true;
598
599
Luau::CodeGen::compile(L, -1, nativeOptions);
600
}
601
602
if (coverageActive())
603
coverageTrack(L, -1);
604
605
if (countersActive())
606
countersTrack(L, -1);
607
608
setupArguments(L, program_argc, program_argv);
609
status = lua_resume(L, NULL, program_argc);
610
}
611
else
612
{
613
status = LUA_ERRSYNTAX;
614
}
615
616
if (status != 0)
617
{
618
std::string error;
619
620
if (status == LUA_YIELD)
621
{
622
error = "thread yielded unexpectedly";
623
}
624
else if (const char* str = lua_tostring(L, -1))
625
{
626
error = str;
627
}
628
629
error += "\nstacktrace:\n";
630
error += lua_debugtrace(L);
631
632
fprintf(stderr, "%s", error.c_str());
633
}
634
635
if (repl)
636
{
637
runReplImpl(L);
638
}
639
lua_pop(GL, 1);
640
return status == 0;
641
}
642
643
static void displayHelp(const char* argv0)
644
{
645
printf("Usage: %s [options] [file list] [-a] [arg list]\n", argv0);
646
printf("\n");
647
printf("When file list is omitted, an interactive REPL is started instead.\n");
648
printf("\n");
649
printf("Available options:\n");
650
printf(" --coverage: collect code coverage while running the code and output results to coverage.out\n");
651
printf(" --counters: collect native counters data while running the code and output results to callgrind.out\n");
652
printf(" -h, --help: Display this usage message.\n");
653
printf(" -i, --interactive: Run an interactive REPL after executing the last script specified.\n");
654
printf(" -O<n>: compile with optimization level n (default 1, n should be between 0 and 2).\n");
655
printf(" -g<n>: compile with debug level n (default 1, n should be between 0 and 2).\n");
656
printf(" --profile[=N]: profile the code using N Hz sampling (default 10000) and output results to profile.out\n");
657
printf(" --timetrace: record compiler time tracing information into trace.json\n");
658
printf(" --codegen: execute code using native code generation\n");
659
printf(" --codegen-perf: execute code using native code generation and profile using perf (only on Linux)\n");
660
printf(" --program-args,-a: declare start of arguments to be passed to the Luau program\n");
661
printf(" --fflags=<flags>: comma-separated list of fast flags to enable/disable (--fflags=true,false,LuauFlag1=true,LuauFlag2=false).\n");
662
}
663
664
static int assertionHandler(const char* expr, const char* file, int line, const char* function)
665
{
666
printf("%s(%d): ASSERTION FAILED: %s\n", file, line, expr);
667
return 1;
668
}
669
670
int replMain(int argc, char** argv)
671
{
672
Luau::assertHandler() = assertionHandler;
673
674
#ifdef _WIN32
675
SetConsoleOutputCP(CP_UTF8);
676
#endif
677
678
int profile = 0;
679
bool coverage = false;
680
bool interactive = false;
681
bool codegenPerf = false;
682
bool counters = false;
683
int program_args = argc;
684
685
for (int i = 1; i < argc; i++)
686
{
687
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0)
688
{
689
displayHelp(argv[0]);
690
return 0;
691
}
692
else if (strcmp(argv[i], "-i") == 0 || strcmp(argv[i], "--interactive") == 0)
693
{
694
interactive = true;
695
}
696
else if (strncmp(argv[i], "-O", 2) == 0)
697
{
698
int level = atoi(argv[i] + 2);
699
if (level < 0 || level > 2)
700
{
701
fprintf(stderr, "Error: Optimization level must be between 0 and 2 inclusive.\n");
702
return 1;
703
}
704
globalOptions.optimizationLevel = level;
705
}
706
else if (strncmp(argv[i], "-g", 2) == 0)
707
{
708
int level = atoi(argv[i] + 2);
709
if (level < 0 || level > 2)
710
{
711
fprintf(stderr, "Error: Debug level must be between 0 and 2 inclusive.\n");
712
return 1;
713
}
714
globalOptions.debugLevel = level;
715
}
716
else if (strcmp(argv[i], "--profile") == 0)
717
{
718
profile = 10000; // default to 10 KHz
719
}
720
else if (strncmp(argv[i], "--profile=", 10) == 0)
721
{
722
profile = atoi(argv[i] + 10);
723
}
724
else if (strcmp(argv[i], "--codegen") == 0)
725
{
726
codegen = true;
727
}
728
else if (strcmp(argv[i], "--codegen-perf") == 0)
729
{
730
codegen = true;
731
codegenPerf = true;
732
}
733
else if (strcmp(argv[i], "--coverage") == 0)
734
{
735
coverage = true;
736
}
737
else if (strcmp(argv[i], "--counters") == 0)
738
{
739
counters = true;
740
}
741
else if (strcmp(argv[i], "--timetrace") == 0)
742
{
743
FFlag::DebugLuauTimeTracing.value = true;
744
}
745
else if (strncmp(argv[i], "--fflags=", 9) == 0)
746
{
747
setLuauFlags(argv[i] + 9);
748
}
749
else if (strcmp(argv[i], "--program-args") == 0 || strcmp(argv[i], "-a") == 0)
750
{
751
program_args = i + 1;
752
break;
753
}
754
else if (argv[i][0] == '-')
755
{
756
fprintf(stderr, "Error: Unrecognized option '%s'.\n\n", argv[i]);
757
displayHelp(argv[0]);
758
return 1;
759
}
760
}
761
762
program_argc = argc - program_args;
763
program_argv = &argv[program_args];
764
765
766
#if !defined(LUAU_ENABLE_TIME_TRACE)
767
if (FFlag::DebugLuauTimeTracing)
768
{
769
fprintf(stderr, "To run with --timetrace, Luau has to be built with LUAU_ENABLE_TIME_TRACE enabled\n");
770
return 1;
771
}
772
#endif
773
774
if (codegenPerf)
775
{
776
#if __linux__
777
char path[128];
778
snprintf(path, sizeof(path), "/tmp/perf-%d.map", getpid());
779
780
// note, there's no need to close the log explicitly as it will be closed when the process exits
781
FILE* codegenPerfLog = fopen(path, "w");
782
783
Luau::CodeGen::setPerfLog(
784
codegenPerfLog,
785
[](void* context, uintptr_t addr, unsigned size, const char* symbol)
786
{
787
FILE* outputFile = static_cast<FILE*>(context);
788
fprintf(outputFile, "%016lx %08x %s\n", long(addr), size, symbol);
789
fflush(outputFile);
790
}
791
);
792
#else
793
fprintf(stderr, "--codegen-perf option is only supported on Linux\n");
794
return 1;
795
#endif
796
}
797
798
if (codegen && !Luau::CodeGen::isSupported())
799
fprintf(stderr, "Warning: Native code generation is not supported in current configuration\n");
800
801
const std::vector<std::string> files = getSourceFiles(argc, argv);
802
803
if (files.empty())
804
{
805
runRepl();
806
return 0;
807
}
808
else
809
{
810
std::unique_ptr<lua_State, void (*)(lua_State*)> globalState(luaL_newstate(), lua_close);
811
lua_State* L = globalState.get();
812
813
setupState(L);
814
815
if (profile)
816
profilerStart(L, profile);
817
818
if (coverage)
819
coverageInit(L);
820
821
if (counters)
822
countersInit(L);
823
824
int failed = 0;
825
826
for (size_t i = 0; i < files.size(); ++i)
827
{
828
bool isLastFile = i == files.size() - 1;
829
failed += !runFile(files[i].c_str(), L, interactive && isLastFile);
830
}
831
832
if (profile)
833
{
834
profilerStop();
835
profilerDump("profile.out");
836
}
837
838
if (coverage)
839
coverageDump("coverage.out");
840
841
if (counters)
842
countersDump("callgrind.out");
843
844
return failed ? 1 : 0;
845
}
846
}
847
848