error.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. /**
  2. * Error Handling Mechanics
  3. */
  4. const TRACE_DEPTH = 24
  5. const CALL_FROM_SCRIPT = 1
  6. const CALL_FROM_OVERLOAD = 2
  7. const CALL_FROM_BUILT_IN = 3
  8. /**
  9. * Unrecoverable Fatal Error
  10. */
  11. class RuntimeError extends Error {
  12. constructor (msg) {
  13. super(msg)
  14. this.name = 'RuntimeError'
  15. }
  16. }
  17. /**
  18. * Recoverable Error Produced by `ensure` Command
  19. */
  20. class EnsureFailed extends Error {
  21. constructor (name, file, row, col) {
  22. super (
  23. `validation for '${name}' failed`
  24. + ` at ${file} (row ${row}, column ${col})`
  25. )
  26. this.name = "EnsureFailed"
  27. }
  28. }
  29. /**
  30. * Recoverable Error Produced by `throw` Commmand
  31. */
  32. class CustomError extends Error {
  33. constructor (message, name = 'CustomError', data = {}) {
  34. let trace = get_trace()
  35. super(message + LF + LF + stringify_trace(trace) + LF)
  36. this.name = name
  37. this.data = data
  38. this.trace = trace
  39. }
  40. }
  41. /**
  42. * Fatal errors are defined here.
  43. * They won't be caught by handle hooks.
  44. */
  45. let FatalErrorClasses = [
  46. RuntimeError, AssertionFailed,
  47. RangeError, ReferenceError, SyntaxError, TypeError
  48. ]
  49. function is_fatal (error) {
  50. if (!(error instanceof Error)) {
  51. return true
  52. }
  53. return exists(FatalErrorClasses, E => error instanceof E)
  54. }
  55. /**
  56. * Call Stack (only used to store debug info)
  57. *
  58. * Fields of frame:
  59. * call_type: Integer, (1: script, 2: overload, 3: built-in)
  60. * desc: String, (description of function)
  61. * file: String, (position of call expression in source code)
  62. * row: Integer, (position of call expression in source code)
  63. * col: Integer (position of call expression in source code)
  64. */
  65. let call_stack = []
  66. function push_call (call_type, desc, file = null, row = -1, col = -1) {
  67. assert(Number.isInteger(call_type))
  68. call_stack.push({ call_type, desc, file, row, col })
  69. }
  70. function pop_call () {
  71. call_stack.pop()
  72. }
  73. function get_top_frame () {
  74. if (call_stack.length > 0) {
  75. return call_stack[call_stack.length-1]
  76. } else {
  77. return null
  78. }
  79. }
  80. function get_trace () {
  81. let info_list = list(map(call_stack, frame => {
  82. let point = 'from <unknown>'
  83. if (frame.call_type == CALL_FROM_SCRIPT) {
  84. // called at script file
  85. let pos = ''
  86. if (frame.row != -1) {
  87. pos = `(row ${frame.row}, column ${frame.col})`
  88. }
  89. point = `from ${frame.file} ${pos}`
  90. } else if (frame.call_type == CALL_FROM_OVERLOAD) {
  91. // specific function called by overloaded function
  92. point = 'from <overload>'
  93. } else if (frame.call_type == CALL_FROM_BUILT_IN) {
  94. // called by built-in function
  95. point = 'from <built-in>'
  96. }
  97. return (frame.desc + LF + INDENT + point)
  98. }))
  99. return info_list
  100. }
  101. function stringify_trace (trace) {
  102. return join(take(rev(trace), TRACE_DEPTH), LF)
  103. }
  104. /**
  105. * Produces a RuntimeError with Stack Backtrace
  106. *
  107. * This function is called by `ensure()` and `panic()`.
  108. * `ensure()` is called by built-in functions to produce fatal error.
  109. * `panic()` is used by `assert` command and `panic` command.
  110. */
  111. function crash (msg) {
  112. let trace = get_trace()
  113. let err = new RuntimeError (
  114. msg + LF + LF + stringify_trace(trace) + LF
  115. )
  116. err.trace = trace
  117. throw err
  118. }
  119. /**
  120. * Crashes if given bool condition is not satisfied
  121. */
  122. function ensure (bool, msg_type, ...args) {
  123. if (bool) { return }
  124. crash(get_msg(msg_type, args))
  125. }
  126. /**
  127. * Crashes with a message
  128. */
  129. function panic (msg) {
  130. // this function is used by `built-in/exception.js`
  131. crash(`panic: ${msg}`)
  132. }
  133. /**
  134. * Gets error message from MSG defined in `msg.js`
  135. */
  136. function get_msg (msg_type, args) {
  137. assert(typeof msg_type == 'string')
  138. assert(args instanceof Array)
  139. let msg = MSG[msg_type]
  140. if (typeof msg == 'string') {
  141. return msg
  142. } else {
  143. assert(typeof msg == 'function')
  144. return msg.apply(null, args)
  145. }
  146. }
  147. /**
  148. * This function is called at the beginning of handle hooks.
  149. *
  150. * Since fatal errors are considered unrecoverable,
  151. * they should not be caught by handle hooks.
  152. * At the beginning of handle hooks, we simply re-throw
  153. * any kind of fatal errors.
  154. */
  155. function enter_handle_hook (error) {
  156. if (is_fatal(error)) {
  157. throw error
  158. }
  159. }
  160. /**
  161. * This function is called at the end of handle hooks.
  162. *
  163. * Because in this language we use function-level error handling,
  164. * if the end of handle hook is reached, it means that
  165. * the error caught by the handle hook is not handled correctly,
  166. * therefore it is necessary to covert the error to a fatal error.
  167. * Note that fatal errors have been thrown at the start of handle hook,
  168. * so the argument `error` can't be a fatal error.
  169. */
  170. function exit_handle_hook (error) {
  171. assert(!is_fatal(error))
  172. throw new RuntimeError(`Unhandled ${error.name}: ${error.message}`)
  173. }