Advertisement
Guest User

plaw

a guest
Jul 28th, 2015
274
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 20.27 KB | None | 0 0
  1. local CompileFile = CompileFile
  2. local CompileString = CompileString
  3. local debug = debug
  4. local error = error
  5. local file = file
  6. local hook = hook
  7. local include = include
  8. local isfunction = isfunction
  9. local math = math
  10. local os = os
  11. local pcall = pcall
  12. local string = string
  13. local table = table
  14. local tonumber = tonumber
  15. local unpack = unpack
  16.  
  17. -- Template for syntax errors
  18. -- The [ERROR] start of it cannot be removed, because that would make the
  19. -- error mechanism remove all square brackets. Only Garry can make that bullshit up.
  20. local synErrTranslation = [=[[ERROR] Lua is unable to understand file "%s" because its author made a mistake around line number %i.
  21. The best help I can give you is this:
  22.  
  23. %s
  24.  
  25. Hints:
  26. %s
  27.  
  28. ------- End of Simplerr error -------
  29. ]=] -- The end is a special string by which simplerr errors are internally recognised
  30.  
  31. -- Template for runtime errors
  32. local runErrTranslation = [=[[ERROR] A runtime error has occurred in "%s" on line %i.
  33. The best help I can give you is this:
  34.  
  35. %s
  36.  
  37. Hints:
  38. %s
  39.  
  40. The responsibility for this error lies with (the authors of) one (or more) of these files:
  41. %s
  42. ------- End of Simplerr error -------
  43. ]=]
  44.  
  45. -- Structure that contains syntax errors and their translations. Catches only the most common errors.
  46. -- Order is important: the structure with the first match is taken.
  47. local synErrs = {
  48. {
  49. match = "'=' expected near '(.*)'",
  50. text = "Right before the '%s', Lua expected to read an '='-sign, but it didn't.",
  51. format = function(m) return m[1] end,
  52. hints = {
  53. "Did you simply forget the '='-sign?",
  54. "Did you forget a comma?",
  55. "Is this supposed to be a local variable?"
  56. }
  57. },
  58. {
  59. match = "'.' expected [(]to close '([{[(])' at line ([0-9-]+)[)] near '(.*)'",
  60. text = "There is an opening '%s' bracket at line %i, but this bracket is never closed or not closed in time. It was expected to be closed before the '%s' at line %i.",
  61. format = function(m, l) return m[1], m[2], m[3], l end,
  62. hints = {
  63. "Did you forget a comma?",
  64. "All open brackets ({, (, [) must have a matching closing bracket. Are you sure it's there?",
  65. "Brackets must be opened and closed in the right order. This will work: ({}), but this won't: ({)}."
  66. }
  67. },
  68. {
  69. match = "'end' expected [(]to close '(.*)' at line ([0-9-]+)[)] near '(.*)'",
  70. text = "An '%s' was started on line %i, but it was never ended or not ended in time. It was expected to be ended before the '%s' at line %i",
  71. format = function(m, l) return m[1], m[2], m[3], l end,
  72. hints = {
  73. "For every if/for/do/while/function there must be an 'end' that closes it."
  74. }
  75. },
  76. {
  77. match = "unfinished string near '(.*)'",
  78. text = "The string '%s' at line %i is opened, but not closed.",
  79. format = function(m, l) return m[1], l end,
  80. hints = {
  81. "A string is a different word for literal text.",
  82. "Strings must be in single or double quotation marks (e.g. 'example', \"example\")",
  83. "A third option for strings is for them to be in double square brackets.",
  84. "Whatever you use (quotations or square brackets), you must not forget that strings are enclosed within a pair of quotation marks/square brackets."
  85. }
  86. },
  87. {
  88. match = "unfinished long string near '(.*)'",
  89. text = "Lua expected to see the end of a multiline string somewhere before the '%s' at line %i.",
  90. format = function(m, l) return m[1], l end,
  91. hints = {
  92. "A string is a different word for literal text.",
  93. "Multiline strings are strings that span over multiple lines.",
  94. "Multiline strings must be enclosed by double square brackets.",
  95. "Whatever you use (quotations or square brackets), you must not forget that strings are enclosed within a pair of quotation marks/square brackets.",
  96. "If you used brackets, the source of the mistake may be somewhere above the reported line."
  97. }
  98. },
  99. {
  100. match = "unfinished long comment near '(.*)'",
  101. text = "Lua expected to see the end of a multiline comment somewhere before the '%s' at line %i.",
  102. format = function(m, l) return m[1], l end,
  103. hints = {
  104. "A comment is text ignored by Lua.",
  105. "Multiline comments are ones that span multiple lines.",
  106. "Multiline comments must be enclosed by either /* and */ or double square brackets.",
  107. "Whatever you use (/**/ or square brackets), you must not forget that once you start a comment, you must end it.",
  108. "The source of the mistake may be somewhere above the reported line."
  109. }
  110. },
  111. -- Generic error messages
  112. {
  113. match = "function arguments expected near '(.*)'",
  114. text = "A function is being called right before '%s', but its arguments are not given.",
  115. format = function(m) return m[1] end,
  116. hints = {
  117. "Did you write 'something:otherthing'? Try changing it to 'something:otherthing()'"
  118. }
  119. },
  120. {
  121. match = "unexpected symbol near '(.*)'",
  122. text = "Right before the '%s', Lua encountered something it could not make sense of.",
  123. format = function(m) return m[1] end,
  124. hints = {"Did you forget something here? (Perhaps a closing bracket)", "Is it a typo?"}
  125. },
  126. {
  127. match = "'(.*)' expected near '(.*)'",
  128. text = "Right before the '%s', Lua expected to read a '%s', but it didn't.",
  129. format = function(m) return m[2], m[1] end,
  130. hints = {"Did you forget a keyword?", "Did you forget a comma?"}
  131. },
  132. {
  133. match = "malformed number near '(.*)'",
  134. text = "Lua attempted to read '%s' as a number, but failed to do so.",
  135. format = function(m) return m[1] end,
  136. hints = {
  137. "Numbers starting with '0x' are hexidecimal.",
  138. "Lua can get confused when doing '<number>..\"some text\"'. Try inserting a space between the number and the '..'."
  139. }
  140. },
  141. }
  142.  
  143. -- Similar structure for runtime errors. Catches only the most common errors.
  144. -- Order is important: the structure with the first match is taken
  145. local runErrs = {
  146. {
  147. match = "table index is nil",
  148. text = "A table is being indexed by something that does not exist (table index is nil).", -- Requires improvement
  149. format = function() end,
  150. hints = {
  151. "The thing between square brackets does not exist (is nil)."
  152. }
  153. },
  154. {
  155. match = "table index is NaN",
  156. text = "A table is being indexed by something that is not really a number (table index is NaN).",
  157. format = function() end,
  158. hints = {
  159. "Did you divide zero by zero thinking it would be funny?"
  160. }
  161. },
  162. {
  163. match = "attempt to index global '(.*)' [(]a nil value[)]",
  164. text = "'%s' is being indexed like it is a table, but in reality it does not exist (is nil).",
  165. format = function(m) return m[1] end,
  166. hints = {
  167. "You either have 'something.somethingElse', 'something[somethingElse]' or 'something:somethingElse(more)'. The 'something' here does not exist."
  168. }
  169. },
  170. {
  171. match = "attempt to index global '(.*)' [(]a (.*) value[)]",
  172. text = "'%s' is being indexed like it is a table, but in reality it is a %s value.",
  173. format = function(m) return m[1], m[2] end,
  174. hints = {
  175. "You either have 'something.somethingElse' or 'something:somethingElse(more)'. The 'something' here is not a table."
  176. }
  177. },
  178. {
  179. match = "attempt to index a nil value",
  180. text = "Something is being indexed like it is a table, but in reality does not exist (is nil).",
  181. format = function() end,
  182. hints = {
  183. "You either have 'something.somethingElse', 'something[somethingElse]' or 'something:somethingElse(more)'. The 'something' here does not exist."
  184. }
  185. },
  186. {
  187. match = "attempt to index a (.*) value",
  188. text = "Something is being indexed like it is a table, but in reality it is a %s value.",
  189. format = function(m) return m[1] end,
  190. hints = {
  191. "You either have 'something.somethingElse', 'something[somethingElse]' or 'something:somethingElse(more)'. The 'something' here is not a table."
  192. }
  193. },
  194. {
  195. match = "attempt to call global '(.*)' [(]a nil value[)]",
  196. text = "'%s' is being called like it is a function, but in reality does not exist (is nil).",
  197. format = function(m) return m[1] end,
  198. hints = {
  199. "You are doing something(<otherstuff>). The 'something' here does not exist."
  200. }
  201. },
  202. {
  203. match = "attempt to call a nil value",
  204. text = "Something is being called like it is a function, but in reality it does not exist (is nil).",
  205. format = function() end,
  206. hints = {
  207. "You are doing something(<otherstuff>). The 'something' here does not exist."
  208. }
  209. },
  210. {
  211. match = "attempt to call global '(.*)' [(]a (.*) value[)]",
  212. text = "'%s' is being called like it is a function, but in reality it is a %s.",
  213. format = function(m) return m[1], m[2] end,
  214. hints = {
  215. "You are doing something(<otherstuff>). The 'something' here is not a function."
  216. }
  217. },
  218. {
  219. match = "attempt to call a (.*) value",
  220. text = "Something is being called like it is a function, but in reality it is a %s.",
  221. format = function(m) return m[1] end,
  222. hints = {
  223. "You are doing something(<otherstuff>). The 'something' here is not a function."
  224. }
  225. },
  226. {
  227. match = "attempt to call field '(.*)' [(]a nil value[)]",
  228. text = "'%s' is being called like it is a function, but in reality it does not exist (is nil).",
  229. format = function(m) return m[1] end,
  230. hints = {
  231. "You are doing either stuff.something(<otherstuff>) or stuff:something(<otherstuff>). The 'something' here does not exist."
  232. }
  233. },
  234. {
  235. match = "attempt to call field '(.*)' [(]a (.*) value[)]",
  236. text = "'%s' is being called like it is a function, but in reality it is a %s.",
  237. format = function(m) return m[1], m[2] end,
  238. hints = {
  239. "You are doing either stuff.something(<otherstuff>) or stuff:something(<otherstuff>). The 'something' here is not a function."
  240. }
  241. },
  242. {
  243. match = "attempt to concatenate global '(.*)' [(]a nil value[)]",
  244. text = "'%s' is being concatenated to something else, but '%s' does not exist (is nil).",
  245. format = function(m) return m[1], m[1] end,
  246. hints = {
  247. "Concatenation looks like this: something .. otherThing. Either something or otherThing does not exist."
  248. }
  249. },
  250. {
  251. match = "attempt to concatenate global '(.*)' [(]a (.*) value[)]",
  252. text = "'%s' is being concatenated to something else, but %s values cannot be concatenated.",
  253. format = function(m) return m[1], m[2] end,
  254. hints = {
  255. "Concatenation looks like this: something .. otherThing. Either something or otherThing is neither string nor number."
  256. }
  257. },
  258. {
  259. match = "attempt to concatenate a nil value",
  260. text = "Two (or more) things are being concatenated and one of them does not exist (is nil).",
  261. format = function() end,
  262. hints = {
  263. "Concatenation looks like this: something .. otherThing. Either something or otherThing does not exist."
  264. }
  265. },
  266. {
  267. match = "attempt to concatenate a (.*) value",
  268. text = "Two (or more) things are being concatenated and one of them is neither string nor number, but a %s.",
  269. format = function(m) return m[1] end,
  270. hints = {
  271. "Concatenation looks like this: something .. otherThing. Either something or otherThing is neither string nor number."
  272. }
  273. },
  274. {
  275. match = "stack overflow",
  276. text = "The stack of function calls has overflowed",
  277. format = function() end,
  278. hints = {
  279. "Most likely infinite recursion.",
  280. "Do you have a function calling itself?"
  281. }
  282. },
  283. {
  284. match = "attempt to compare two (.*) values",
  285. text = "A comparison is being made between two %s values. They cannot be compared.",
  286. format = function(m) return m[1] end,
  287. hints = {
  288. "This error usually occurs when two incompatible things are being compared.",
  289. "'comparison' in this context means one of <, >, <=, >= (smaller than, greater than, etc.)"
  290. }
  291. },
  292. {
  293. match = "attempt to compare (.*) with (.*)",
  294. text = "A comparison is being made between a %s and a %s. This is not possible.",
  295. format = function(m) return m[1], m[2] end,
  296. hints = {
  297. "This error usually occurs when two incompatible things are being compared.",
  298. "'Comparison' in this context means one of <, >, <=, >= (smaller than, greater than, etc.)"
  299. }
  300. },
  301. {
  302. match = "attempt to perform arithmetic on a (.*) value",
  303. text = "Arithmetic operations are being performed on a %s. This is not possible.",
  304. format = function(m) return m[1] end,
  305. hints = {
  306. "'Arithmetic' in this context means adding, multiplying, dividing, etc."
  307. }
  308. },
  309. {
  310. match = "attempt to get length of global '(.*)' [(]a nil value[)]",
  311. text = "The length of '%s' is requested as if it is a table, but in reality it does not exist (is nil).",
  312. format = function(m) return m[1] end,
  313. hints = {
  314. "You are doing #something. The 'something' here is does not exist."
  315. }
  316. },
  317. {
  318. match = "attempt to get length of global '(.*)' [(]a (.*) value[)]",
  319. text = "The length of '%s' is requested as if it is a table, but in reality it is a %s.",
  320. format = function(m) return m[1], m[2] end,
  321. hints = {
  322. "You are doing #something. The 'something' here is not a table."
  323. }
  324. },
  325. {
  326. match = "attempt to get length of a nil value",
  327. text = "The length of something is requested as if it is a table, but in reality it does not exist (is nil).",
  328. format = function(m) return m[1] end,
  329. hints = {
  330. "You are doing #something. The 'something' here is does not exist."
  331. }
  332. },
  333. {
  334. match = "attempt to get length of a (.*) value",
  335. text = "The length of something is requested as if it is a table, but in reality it is a %s.",
  336. format = function(m) return m[1] end,
  337. hints = {
  338. "You are doing #something. The 'something' here is not a table."
  339. }
  340. },
  341. }
  342.  
  343. module("simplerr")
  344.  
  345. -- Get a nicely formatted stack trace. Start is where to start numbering
  346. local function getStack(i, start)
  347. i = i or 1
  348. start = start or 1
  349. local stack = {}
  350.  
  351. -- Invariant: stack level (i + count) >= 2 and <= last stack item
  352. for count = 1, math.huge do -- user visible count
  353. info = debug.getinfo(i + count, "Sln")
  354. if not info then break end
  355.  
  356. table.insert(stack, string.format("\t%i. %s on line %s", start + count - 1, info.short_src, info.currentline or "unknown"))
  357. end
  358.  
  359. return table.concat(stack, "\n")
  360. end
  361.  
  362. -- Translate a runtime error to simplerr format.
  363. -- Decorate with e.g. wrapError to have it actually throw the error.
  364. function runError(msg, stackNr, hints, path, line, stack)
  365. stackNr = stackNr or 1
  366. hints = hints or {"No hints, sorry."}
  367. hints = "\t- " .. table.concat(hints, "\n\t- ")
  368.  
  369. if not path and not line then
  370. local info = debug.getinfo(stackNr + 1, "Sln")
  371. path = info.short_src
  372. line = info.currentline
  373. end
  374.  
  375. return false, string.format(runErrTranslation, path, line, msg, hints, stack or getStack(stackNr + 1))
  376. end
  377.  
  378. -- Translate the message of an error
  379. local function translateMsg(msg, path, line, errs)
  380. local res
  381. local hints = {"No hints, sorry."}
  382.  
  383. for i = 1, #errs do
  384. local trans = errs[i]
  385. if not string.find(msg, trans.match) then continue end
  386.  
  387. -- translate <eof>
  388. msg = string.Replace(msg, "<eof>", "end of the file")
  389.  
  390. res = string.format(trans.text, trans.format({string.match(msg, trans.match)}, line, path))
  391. hints = trans.hints
  392.  
  393. break
  394. end
  395.  
  396. return res or msg, "\t- " .. table.concat(hints, "\n\t- ")
  397. end
  398.  
  399. -- Translate an error into a language understandable by non-programmers
  400. local function translateError(path, err, translation, errs, stack)
  401. -- Using .* instead of path because path may be wrong when error is called
  402. local line, msg = string.match(err, ".*:([0-9-]+): (.*)")
  403. line = tonumber(line)
  404.  
  405. local msg, hints = translateMsg(msg, path, line, errs)
  406. local res = string.format(translation, path, line, msg, hints, stack)
  407. return res
  408. end
  409.  
  410. -- Call a function and catch immediate runtime errors
  411. function safeCall(f, ...)
  412. local res = {pcall(f, ...)}
  413. local succ, err = res[1], res[2]
  414.  
  415. if succ then return unpack(res) end
  416.  
  417. local info = debug.getinfo(f)
  418. local path = info.short_src
  419.  
  420. -- Investigate the stack. Not using path in match because calls to error can give a different path
  421. local line = string.match(err, ".*:([0-9-]+)")
  422. local stack = string.format("\t1. %s on line %s\n", path, line) .. getStack(2, 2) -- add called func to stack
  423.  
  424. -- Line and source info aren't always in the error
  425. if not line then
  426. line = info.currentline
  427. err = string.format("%s:%s: %s", path, line, err)
  428. end
  429.  
  430. -- Skip translation if the error is already a simplerr error
  431. -- This prevents nested simplerr errors when runError is called by a file loaded by runFile
  432. local mustTranslate = not string.find(err, "------- End of Simplerr error -------")
  433. return false, mustTranslate and translateError(path, err, runErrTranslation, runErrs, stack) or err
  434. end
  435.  
  436. -- Run a file or explain its syntax errors in layman's terms
  437. -- Returns bool succeed, [string error]
  438. -- Do NOT use this on clientside files.
  439. -- Clientside files sent by the server cannot be read using file.Read unless you're the host of a listen server
  440. function runFile(path)
  441. if not file.Exists(path, "LUA") then error(string.format("Could not run file '%s' (file not found)", path)) end
  442. local contents = file.Read(path, "LUA")
  443.  
  444. -- Files can make a comment containing #NoSimplerr# to disable simplerr (and thus enable autorefresh)
  445. if string.find(contents, "#NoSimplerr#") then include(path) return true end
  446.  
  447. -- Catch syntax errors with CompileString
  448. local err = CompileString(contents, path, false)
  449.  
  450. -- CompileString returns the following string whenever a file is empty: Invalid script - or too short.
  451. -- It also prints: Not running script <path> - it's too short.
  452. -- If so, do nothing.
  453. if err == "Invalid script - or too short." then return true end
  454.  
  455. -- No syntax errors, check for immediate runtime errors using CompileFile
  456. -- Using the function CompileString returned leads to relative path trouble
  457. if isfunction(err) then return safeCall(CompileFile(path), path) end
  458.  
  459. return false, translateError(path, err, synErrTranslation, synErrs)
  460. end
  461.  
  462. -- Error wrapper: decorator for runFile and safeCall that throws an error on failure.
  463. -- Breaks execution. Must be the last decorator.
  464. function wrapError(succ, err, ...)
  465. if succ then return succ, err, ... end
  466.  
  467. error(err)
  468. end
  469.  
  470. -- Hook wrapper: Calls a hook on error
  471. function wrapHook(succ, err, ...)
  472. if not succ then hook.Call("onSimplerrError", nil, err) end
  473.  
  474. return succ, err, ...
  475. end
  476.  
  477. -- Logging wrapper: decorator for runFile and safeCall that logs failures.
  478. local log = {}
  479. function wrapLog(succ, err, ...)
  480. if succ then return succ, err, ... end
  481.  
  482. local data = {
  483. err = err,
  484. time = os.time()
  485. }
  486.  
  487. table.insert(log, data)
  488.  
  489. return succ, err, ...
  490. end
  491.  
  492. -- Retrieve the log
  493. function getLog() return log end
  494.  
  495. -- Clear the log
  496. function clearLog() log = {} end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement