PageRenderTime 46ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/croc.cpp

http://github.com/JarrettBillingsley/Croc
C++ | 738 lines | 707 code | 31 blank | 0 comment | 16 complexity | bad29883cb72161058b480f2ea928c6f MD5 | raw file
  1. #include <assert.h>
  2. #include <setjmp.h>
  3. #ifdef _WIN32
  4. #include "windows.h"
  5. #else
  6. #include <signal.h>
  7. #endif
  8. #include "croc/api.h"
  9. const char* Src =
  10. R"xxxx(//#line 14
  11. local _loadLibs = _croctmp._loadLibs
  12. local _setInterruptibleThread = _croctmp._setInterruptibleThread
  13. local _haltWasTriggered = _croctmp._haltWasTriggered
  14. local _resetInterrupt = _croctmp._resetInterrupt
  15. local _loadFileLib = _croctmp._loadFileLib
  16. local Version = "Croc alpha"
  17. if(#_croctmp._addons)
  18. Version ~= " (Available addons: " ~ ", ".join(_croctmp._addons) ~ ")"
  19. local namespace ExitCode
  20. {
  21. OK = 0
  22. BadArg = 1
  23. OtherError = 2
  24. }
  25. local ShortUsage = [=[
  26. Usage:
  27. croc [options] enter REPL
  28. croc [options] filename [args] run <filename> with args
  29. croc [options] -e "string" run "string"
  30. croc --compile outpath filename compile module to bytecode
  31. croc --doctable outname filename extract doctable as JSON
  32. croc --help (or croc -h) prints the full help and exits
  33. croc --version (or croc -v) prints version and exits
  34. Options:
  35. -d (or --debug) load debug lib
  36. --docs=<on|off|default> doc comment mode
  37. -I "path" add import path
  38. -l dotted.module.name import module
  39. --safe safe libs only (overrides -d)]=]
  40. local LongUsage = [=[
  41. This is the Croc standalone interpreter. It can be run in several different
  42. modes, and the modes in which code is executed can also take options.
  43. The execution options are as follows:
  44. -d, --debug
  45. Load the debug library.
  46. --docs=<on|off|default>
  47. Control whether documentation comments are extracted and attached to
  48. objects. The default is to enable them in interactive mode and disable
  49. them in file and string mode.
  50. -I "path"
  51. Specifies an import path to search when importing modules. You can
  52. add multiple import paths.
  53. -l dotted.module.name
  54. Imports the module "dotted.module.name" before running your code. You
  55. can have multiple imports.
  56. --safe
  57. Only load safe libraries. Overrides -d (prevents the debug library
  58. from being loaded even if -d is specified).
  59. The execution modes are as follows:
  60. croc [options]
  61. Interactive mode. You are given a REPL where the code you type is
  62. immediately executed (once you type enough to form a valid statement
  63. or expression). You can exit this by calling 'exit()' or by typing
  64. Ctrl+D and hitting enter.
  65. croc [options] filename [args...]
  66. File mode. 'filename' can be either a path to a .croc or .croco file,
  67. or it can be a dotted.module.name. In either case, the given file or
  68. module is loaded/imported, and its main() function (if any) will be
  69. called with 'args' as the arguments. All the arguments will be
  70. strings. If the main() function returns an integer, it will be used
  71. as the exit code.
  72. croc [options] -e "string"
  73. String mode. 'string' will be executed as if you typed it in at the
  74. REPL (it will be parsed as an expression or statement, whichever is
  75. appropriate).
  76. croc --compile outpath filename
  77. Compiles the module given by 'filename' (which must be an actual
  78. path), and then uses the serializeModule function in the serialization
  79. library to output the resulting funcdef to a '.croco' file to the
  80. directory 'outpath'.
  81. If 'outpath' is -, the output file will be in the same directory as
  82. the input. Otherwise, it must be a directory.
  83. croc --doctable outname filename
  84. Extracts documentation comments from the module given by 'filename'
  85. (which must be an actual path) and saves the documentation table for
  86. the file into 'outname' as JSON.
  87. If 'outname' is -, then the output JSON will be sent to stdout. If
  88. 'filename' is -, then the source will be read from stdin. If
  89. 'filename' is of the form '-name', then stdin will be read, but the
  90. name following the dash will be used as the filename of the module
  91. (i.e. the 'file' member of the doctables).
  92. croc -h
  93. croc --help
  94. Prints this message and exit.
  95. croc -v
  96. croc --version
  97. Prints the version info and exit.
  98. Any invalid options are an error. Any extra arguments after options such as -e
  99. and --compile are ignored.
  100. Exit codes:
  101. 0 means execution completed successfully.
  102. 1 means there was an invalid program argument.
  103. 2 means there was some error during execution.
  104. 3 is reserved for something really bad happening.
  105. ]=]
  106. local function printVersion()
  107. {
  108. writeln(Version)
  109. }
  110. local function printUsage(full: bool)
  111. {
  112. printVersion()
  113. write(full ? LongUsage : ShortUsage)
  114. }
  115. local function argError(ret, fmt: string, vararg)
  116. {
  117. write("\nError: ")
  118. writefln(fmt, vararg)
  119. writeln()
  120. printUsage(false)
  121. ret.stop = true
  122. ret.failed = true
  123. return ret
  124. }
  125. local function parseArguments(args: array)
  126. {
  127. local ret =
  128. {
  129. failed = false
  130. stop = false
  131. debugEnabled = false
  132. safe = false
  133. docsEnabled = "default"
  134. inputFile = ""
  135. args = []
  136. docOutfile = ""
  137. crocoOutfile = ""
  138. execStr = ""
  139. exec = false
  140. }
  141. if(#args == 0)
  142. return ret
  143. local i = 0
  144. switch(args[0])
  145. {
  146. case "-v", "--version":
  147. printVersion()
  148. ret.stop = true
  149. return ret
  150. case "-h", "--help":
  151. printUsage(true)
  152. ret.stop = true
  153. return ret
  154. case "--compile":
  155. i += 2
  156. if(i >= #args)
  157. return argError(ret, "--compile must be followed by two arguments");
  158. ret.crocoOutfile = args[i - 1]
  159. ret.inputFile = args[i]
  160. return ret
  161. case "--doctable":
  162. i += 2
  163. if(i >= #args)
  164. return argError(ret, "--doctable must be followed by two arguments");
  165. ret.docOutfile = args[i - 1]
  166. ret.inputFile = args[i]
  167. return ret
  168. default:
  169. break
  170. }
  171. for( ; i < #args; i++)
  172. {
  173. switch(args[i])
  174. {
  175. case "-v", "--version", "-h", "--help", "--compile", "--doctable":
  176. return argError(ret, "'{}' flag may not be preceded by options".format(args[i]))
  177. case "-I":
  178. i++
  179. if(i >= #args)
  180. return argError(ret, "-I must be followed by a path")
  181. modules.path ~= ';' ~ args[i]
  182. continue
  183. case "-d", "--debug":
  184. ret.debugEnabled = true
  185. continue
  186. case "-e":
  187. i++
  188. if(i >= #args)
  189. return argError(ret, "-e must be followed by a string")
  190. ret.exec = true
  191. ret.execStr = args[i]
  192. return ret
  193. case "--safe":
  194. ret.safe = true
  195. continue
  196. default:
  197. if(args[i].startsWith("--docs"))
  198. {
  199. local _, eq, mode = args[i].partition('=')
  200. if(eq is '' or mode is '')
  201. return argError(ret, "Malfomed flag: '{}'", args[i]);
  202. switch(mode)
  203. {
  204. case "on", "off", "default":
  205. ret.docsEnabled = mode
  206. break
  207. default:
  208. return argError(ret, "Invalid mode '{}' (must be on, off, or default)", mode)
  209. }
  210. continue
  211. }
  212. else if(args[i].startsWith("-"))
  213. return argError(ret, "Unknown flag '{}'", args[i])
  214. else
  215. {
  216. ret.inputFile = args[i]
  217. ret.args = args[i + 1 ..]
  218. break
  219. }
  220. }
  221. break
  222. }
  223. return ret
  224. }
  225. local function doDocgen(inputFile: string, outputFile: string)
  226. {
  227. local dt
  228. try
  229. {
  230. local src, name
  231. if(inputFile.startsWith("-"))
  232. {
  233. if(#inputFile == 1)
  234. name = "stdin"
  235. else
  236. name = inputFile[1 ..]
  237. src = text.getCodec("utf8").decode(console.stdin.getStream().readAll())
  238. }
  239. else
  240. {
  241. name = inputFile
  242. src = file.readTextFile(inputFile)
  243. }
  244. compiler.setFlags("all")
  245. local fd, modName
  246. fd, modName, dt = compiler.compileModuleDT(src, name)
  247. }
  248. catch(e)
  249. {
  250. writefln("Error: {}", e)
  251. return ExitCode.OtherError
  252. }
  253. try
  254. {
  255. local f = (outputFile is "-") ?
  256. console.stdout :
  257. stream.TextWriter(stream.BufferedOutStream(file.outFile(outputFile, "c")))
  258. json.writeJSON(f, dt, true)
  259. f.writeln()
  260. f.flush()
  261. if(f is not console.stdout)
  262. f.getStream().close()
  263. }
  264. catch(e)
  265. {
  266. writefln("Error: {}", e)
  267. return ExitCode.OtherError
  268. }
  269. return ExitCode.OK
  270. }
  271. local function filenameOf(p: string)
  272. {
  273. local _, ret = path.splitAtLastSep(p)
  274. return ret
  275. }
  276. local function doCompile(inputFile: string, outputFile: string)
  277. {
  278. if(not inputFile.endsWith(".croc") or not file.exists(inputFile))
  279. {
  280. writefln("'{}' is not a valid input filename")
  281. return ExitCode.BadArg
  282. }
  283. if(outputFile is "-")
  284. outputFile = inputFile ~ 'o'
  285. else
  286. {
  287. if(not file.exists(outputFile) or file.fileType(outputFile) is not "dir")
  288. {
  289. writefln("'{}' does not name a directory", outputFile)
  290. return ExitCode.BadArg
  291. }
  292. outputFile = path.join(outputFile, filenameOf(inputFile) ~ 'o')
  293. }
  294. try
  295. {
  296. local src = file.readTextFile(inputFile)
  297. local fd, modName = compiler.compileModule(src, inputFile)
  298. local out = stream.BufferedOutStream(file.outFile(outputFile, "c"))
  299. serialization.serializeModule(fd, modName, out)
  300. out.flush()
  301. out.close()
  302. }
  303. catch(e)
  304. {
  305. writefln("Error: {}", e)
  306. return ExitCode.OtherError
  307. }
  308. return ExitCode.OK
  309. }
  310. local function unloadFileLib()
  311. {
  312. hash.remove(modules.loaded, 'file')
  313. hash.remove(modules.customLoaders, 'file')
  314. hash.remove(_G, 'file')
  315. gc.collect()
  316. }
  317. local function doFile(isSafe: bool, inputFile: string, docsEnabled: string, args: array)
  318. {
  319. if(docsEnabled is "on")
  320. compiler.setFlags("alldocs")
  321. try
  322. {
  323. local modToRun = inputFile
  324. if(inputFile.endsWith(".croc") or inputFile.endsWith(".croco"))
  325. {
  326. local fd, modName
  327. if(isSafe)
  328. _loadFileLib();
  329. if(inputFile[-1] is 'o')
  330. fd, modName = serialization.deserializeModule(stream.MemblockStream(file.readMemblock(inputFile)))
  331. else
  332. fd, modName = compiler.compileModule(file.readTextFile(inputFile), inputFile)
  333. if(isSafe)
  334. unloadFileLib();
  335. modules.customLoaders[modName] = fd
  336. modToRun = modName
  337. }
  338. local ret = modules.runMain(modules.load(modToRun), args.expand())
  339. if(isInt(ret))
  340. return ret
  341. }
  342. catch(e)
  343. {
  344. writefln("Error: {}", e)
  345. writeln(e.tracebackString())
  346. return ExitCode.OtherError
  347. }
  348. return ExitCode.OK
  349. }
  350. local namespace REPL : _G {}
  351. local class ReplInout : repl.ConsoleInout
  352. {
  353. _thread
  354. function init()
  355. {
  356. :_thread = null
  357. }
  358. function cleanup()
  359. {
  360. :_thread = null
  361. }
  362. function run(fd: funcdef)
  363. {
  364. local f = fd.close(REPL)
  365. if(:_thread)
  366. {
  367. assert(:_thread.isDead())
  368. :_thread.reset(f)
  369. }
  370. else
  371. :_thread = thread.new(f)
  372. _setInterruptibleThread(:_thread)
  373. local rets = []
  374. while(not :_thread.isDead())
  375. {
  376. try
  377. rets.set((:_thread)())
  378. catch(e)
  379. {
  380. if(#e.traceback > 0)
  381. e.traceback = e.traceback[0 .. e.traceback.findIf(\tb -> tb.file.startsWith("<croc>.run"))]
  382. exceptions.rethrow(e)
  383. }
  384. }
  385. if(_haltWasTriggered())
  386. {
  387. :writeln("Halted by keyboard interrupt.")
  388. _resetInterrupt()
  389. return
  390. }
  391. return rets.expand()
  392. }
  393. }
  394. local function doInteractive(docsEnabled: string)
  395. {
  396. if(docsEnabled is not "off")
  397. compiler.setFlags("alldocs")
  398. printVersion()
  399. try
  400. repl.interactive(ReplInout())
  401. catch(e)
  402. {
  403. writefln("Error: {}", e)
  404. writeln(e.tracebackString())
  405. return ExitCode.OtherError
  406. }
  407. return ExitCode.OK
  408. }
  409. local function doOneLine(code: string)
  410. {
  411. try
  412. {
  413. local needMore, e = repl.runString(ReplInout(), code)
  414. if(needMore)
  415. {
  416. writefln("Error: {}", e)
  417. writeln(e.tracebackString())
  418. return ExitCode.OtherError
  419. }
  420. }
  421. catch(e)
  422. {
  423. writefln("Error: {}", e)
  424. writeln(e.tracebackString())
  425. return ExitCode.OtherError
  426. }
  427. return ExitCode.OK
  428. }
  429. return function main(args: array)
  430. {
  431. local params = parseArguments(args)
  432. if(params.stop)
  433. return params.failed ? ExitCode.BadArg : ExitCode.OK
  434. else if(#params.docOutfile)
  435. {
  436. // Need file lib
  437. _loadLibs(false, false)
  438. return doDocgen(params.inputFile, params.docOutfile)
  439. }
  440. else if(#params.crocoOutfile)
  441. {
  442. // Need file lib
  443. _loadLibs(false, false)
  444. return doCompile(params.inputFile, params.crocoOutfile)
  445. }
  446. _loadLibs(params.safe, params.debugEnabled)
  447. if(params.exec)
  448. return doOneLine(params.execStr)
  449. else if(#params.inputFile)
  450. return doFile(params.safe, params.inputFile, params.docsEnabled, params.args)
  451. else
  452. return doInteractive(params.docsEnabled)
  453. }
  454. )xxxx";
  455. word_t _loadLibs(CrocThread* t)
  456. {
  457. auto isSafe = croc_ex_checkBoolParam(t, 1);
  458. auto loadDebug = croc_ex_checkBoolParam(t, 2);
  459. if(isSafe)
  460. croc_vm_loadAvailableAddonsExcept(t, CrocAddons_Unsafe);
  461. else
  462. {
  463. croc_vm_loadUnsafeLibs(t, loadDebug ? CrocUnsafeLib_ReallyAll : CrocUnsafeLib_All);
  464. croc_vm_loadAllAvailableAddons(t);
  465. }
  466. return 0;
  467. }
  468. word_t _loadFileLib(CrocThread* t)
  469. {
  470. croc_vm_loadUnsafeLibs(t, CrocUnsafeLib_File);
  471. return 0;
  472. }
  473. CrocThread* _interruptThread = nullptr;
  474. bool _triggered = false;
  475. void _triggerIt()
  476. {
  477. if(!_triggered && _interruptThread)
  478. {
  479. croc_thread_pendingHalt(_interruptThread);
  480. _triggered = true;
  481. }
  482. }
  483. #ifdef _WIN32
  484. BOOL WINAPI sigHandler(DWORD type)
  485. {
  486. if(type == CTRL_C_EVENT)
  487. {
  488. _triggerIt();
  489. return TRUE;
  490. }
  491. return FALSE;
  492. }
  493. #else
  494. void sigHandler(int type)
  495. {
  496. (void)type;
  497. _triggerIt();
  498. signal(SIGINT, &sigHandler);
  499. }
  500. #endif
  501. word_t _setInterruptibleThread(CrocThread* t)
  502. {
  503. croc_ex_checkParam(t, 1, CrocType_Thread);
  504. _interruptThread = croc_getThread(t, 1);
  505. _triggered = false;
  506. return 0;
  507. }
  508. word_t _haltWasTriggered(CrocThread* t)
  509. {
  510. croc_pushBool(t, _triggered);
  511. return 1;
  512. }
  513. word_t _resetInterrupt(CrocThread* t)
  514. {
  515. (void)t;
  516. _triggered = false;
  517. return 0;
  518. }
  519. jmp_buf unhandled;
  520. word_t unhandledEx(CrocThread* t)
  521. {
  522. (void)t;
  523. longjmp(unhandled, 1);
  524. return 0;
  525. }
  526. int main(int argc, char** argv)
  527. {
  528. #ifdef _WIN32
  529. if(!SetConsoleCtrlHandler(&sigHandler, TRUE))
  530. {
  531. fprintf(stderr, "Could not set Ctrl+C handler");
  532. return 3;
  533. }
  534. #else
  535. if(signal(SIGINT, &sigHandler) == SIG_ERR)
  536. {
  537. fprintf(stderr, "Could not set SIGINT handler");
  538. return 3;
  539. }
  540. #endif
  541. auto t = croc_vm_openDefault();
  542. croc_function_new(t, "_unhandledEx", 1, &unhandledEx, 0);
  543. croc_eh_setUnhandledExHandler(t);
  544. croc_popTop(t);
  545. if(setjmp(unhandled) == 0)
  546. {
  547. croc_table_new(t, 0);
  548. croc_function_new(t, "_loadLibs", 2, &_loadLibs, 0);
  549. croc_fielda(t, -2, "_loadLibs");
  550. croc_function_new(t, "_loadFileLib", 0, &_loadFileLib, 0);
  551. croc_fielda(t, -2, "_loadFileLib");
  552. croc_function_new(t, "_setInterruptibleThread", 1, &_setInterruptibleThread, 0);
  553. croc_fielda(t, -2, "_setInterruptibleThread");
  554. croc_function_new(t, "_haltWasTriggered", 0, &_haltWasTriggered, 0);
  555. croc_fielda(t, -2, "_haltWasTriggered");
  556. croc_function_new(t, "_resetInterrupt", 0, &_resetInterrupt, 0);
  557. croc_fielda(t, -2, "_resetInterrupt");
  558. auto start = croc_getStackSize(t);
  559. for(auto addon = croc_vm_includedAddons(); *addon != nullptr; addon++)
  560. croc_pushString(t, *addon);
  561. croc_array_newFromStack(t, croc_getStackSize(t) - start);
  562. croc_fielda(t, -2, "_addons");
  563. croc_newGlobal(t, "_croctmp");
  564. auto slot = croc_namespace_new(t, "<croc>");
  565. croc_ex_loadStringWithEnv(t, Src, "<croc>");
  566. croc_pushNull(t);
  567. croc_call(t, -2, 1);
  568. croc_vm_pushGlobals(t);
  569. croc_pushString(t, "_croctmp");
  570. croc_removeKey(t, -2);
  571. croc_popTop(t);
  572. assert((uword_t)slot == croc_getStackSize(t) - 1);
  573. croc_pushNull(t);
  574. for(int i = 1; i < argc; i++)
  575. croc_pushString(t, argv[i]);
  576. croc_array_newFromStack(t, argc - 1);
  577. int ret;
  578. if(croc_tryCall(t, slot, 1) == CrocCallRet_Error)
  579. {
  580. fprintf(stderr, "-------- Error --------\n");
  581. croc_pushToString(t, -1);
  582. fprintf(stderr, "%s\n", croc_getString(t, -1));
  583. croc_popTop(t);
  584. croc_dupTop(t);
  585. croc_pushNull(t);
  586. croc_methodCall(t, -2, "tracebackString", 1);
  587. fprintf(stderr, "%s\n", croc_getString(t, -1));
  588. ret = 2;
  589. }
  590. else
  591. ret = croc_getInt(t, -1);
  592. croc_vm_close(t);
  593. return ret;
  594. }
  595. else
  596. {
  597. fprintf(stderr, "-------- Fatal Error --------\n");
  598. croc_pushToString(t, -1);
  599. fprintf(stderr, "%s\n", croc_getString(t, -1));
  600. croc_popTop(t);
  601. croc_dupTop(t);
  602. croc_pushNull(t);
  603. croc_methodCall(t, -2, "tracebackString", 1);
  604. fprintf(stderr, "%s\n", croc_getString(t, -1));
  605. return 3;
  606. }
  607. }