LambdaDrawer.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. function deepCopy(obj) {
  2. if (typeof obj === 'object') {
  3. const newObj = {}
  4. for (const prop in obj) {
  5. newObj[prop] = deepCopy(obj[prop])
  6. }
  7. return newObj
  8. } else {
  9. return obj
  10. }
  11. }
  12. function LambdaDrawer(id) {
  13. const graph = document.getElementById(id)
  14. const ctx = graph.getContext('2d')
  15. const defaultAlign = 'center'
  16. ctx.font = '25px Arial'
  17. ctx.textAlign = defaultAlign
  18. ctx.textBaseline = 'middle'
  19. // config setting
  20. const r = 30
  21. const layerGap = 40
  22. const signWidth = 10
  23. // functions
  24. const isId = (id) => id.id != null
  25. const isAbs = (abs) => abs.param != null
  26. const isApp = (app) => app.left != null
  27. const nodeBottom = (ast) => [ast.cx, ast.cy + r]
  28. const nodeTop = (ast) => [ast.cx, ast.cy - r]
  29. const astOperation = (ast, op) => {
  30. if (isId(ast)) {
  31. return op.id()
  32. } else if (isAbs(ast)) {
  33. return op.abs()
  34. } else if (isApp(ast)) {
  35. return op.app()
  36. } else {
  37. throw new Error('unknown ast type')
  38. }
  39. }
  40. const astToString = (ast) => {
  41. return astOperation(ast, {
  42. id: () => ast.name,
  43. abs: () => `λ${ast.param.name}.${astToString(ast.body)}`,
  44. app: () => `(${astToString(ast.left)} ${astToString(ast.right)})`,
  45. })
  46. }
  47. const setSize = (ast) => {
  48. ast.str = astToString(ast)
  49. return astOperation(ast, {
  50. id() {
  51. // n
  52. ast.width = 2 * r
  53. ast.height = 2 * r
  54. },
  55. abs() {
  56. setSize(ast.param)
  57. setSize(ast.body)
  58. // \param.(body)
  59. ast.width = ast.param.width + ast.body.width + 4 * signWidth
  60. ast.height = ast.body.height + 2 * r + layerGap
  61. },
  62. app() {
  63. // (left,right)
  64. setSize(ast.left)
  65. setSize(ast.right)
  66. ast.width = ast.left.width + ast.right.width + 3 * signWidth
  67. ast.height =
  68. Math.max(ast.left.height, ast.right.height) + 2 * r + layerGap
  69. },
  70. })
  71. }
  72. const setPosition = (ast, x, y) => {
  73. ast.x = x
  74. ast.y = y
  75. return astOperation(ast, {
  76. id() {
  77. // n
  78. ast.cx = x + r
  79. ast.cy = y + r
  80. },
  81. abs() {
  82. // \param.(body)
  83. const nextLayerY = y + 2 * r + layerGap
  84. setPosition(ast.param, x + signWidth, nextLayerY)
  85. setPosition(ast.body, x + ast.param.width + 3 * signWidth, nextLayerY)
  86. ast.cx = (ast.param.cx + ast.body.cx) / 2
  87. ast.cy = y + r
  88. },
  89. app() {
  90. // (left,right)
  91. const nextLayerY = y + 2 * r + layerGap
  92. setPosition(ast.left, x + signWidth, nextLayerY)
  93. setPosition(ast.right, x + ast.left.width + 2 * signWidth, nextLayerY)
  94. ast.cx = (ast.left.cx + ast.right.cx) / 2
  95. ast.cy = y + r
  96. },
  97. })
  98. }
  99. const drawText = (value, cx, cy, align = defaultAlign) => {
  100. ctx.textAlign = align
  101. ctx.fillText(value, cx, cy)
  102. ctx.textAlign = defaultAlign
  103. }
  104. const drawLine = (sx, sy, fx, fy) => {
  105. ctx.beginPath()
  106. ctx.moveTo(sx, sy)
  107. ctx.lineTo(fx, fy)
  108. ctx.stroke()
  109. }
  110. const drawNode = (cx, cy, r, value) => {
  111. ctx.beginPath()
  112. ctx.arc(cx, cy, r, 0, 2 * Math.PI)
  113. ctx.stroke()
  114. ctx.fillText(value, cx, cy)
  115. }
  116. const _drawAst = (ast) => {
  117. astOperation(ast, {
  118. id() {
  119. const tag = ast.id >= 0 ? `${ast.name}:${ast.id}` : ast.name
  120. drawNode(ast.cx, ast.cy, r, tag)
  121. },
  122. abs() {
  123. drawNode(ast.cx, ast.cy, r, 'Abs')
  124. _drawAst(ast.param)
  125. _drawAst(ast.body)
  126. const [sx, sy] = nodeBottom(ast)
  127. drawLine(sx, sy, ...nodeTop(ast.param))
  128. drawLine(sx, sy, ...nodeTop(ast.body))
  129. },
  130. app() {
  131. drawNode(ast.cx, ast.cy, r, 'App')
  132. _drawAst(ast.left)
  133. _drawAst(ast.right)
  134. const [sx, sy] = nodeBottom(ast)
  135. drawLine(sx, sy, ...nodeTop(ast.left))
  136. drawLine(sx, sy, ...nodeTop(ast.right))
  137. },
  138. })
  139. }
  140. const drawAstStr = (ast) => {
  141. const x = ast.cx + r + signWidth / 2
  142. astOperation(ast, {
  143. id() {},
  144. abs() {
  145. drawText(ast.str, x, ast.cy, 'left')
  146. drawAstStr(ast.body)
  147. },
  148. app() {
  149. drawText(ast.str, x, ast.cy, 'left')
  150. drawAstStr(ast.left)
  151. drawAstStr(ast.right)
  152. },
  153. })
  154. }
  155. const drawAstInfo = (ast) => {
  156. astOperation(ast, {
  157. id() {
  158. drawText(ast.name, ast.cx, 25)
  159. },
  160. abs() {
  161. drawAstInfo(ast.param)
  162. drawAstInfo(ast.body)
  163. const signOffset = signWidth / 2
  164. drawText('λ', ast.x + signOffset, 25)
  165. drawText('.', ast.param.cx + r + signOffset, 25)
  166. drawText('(', ast.body.x - signOffset, 25)
  167. drawText(')', ast.body.x + ast.body.width + signOffset, 25)
  168. },
  169. app() {
  170. drawAstInfo(ast.left)
  171. drawAstInfo(ast.right)
  172. const signOffset = signWidth / 2
  173. drawText('(', ast.x + signOffset, 25)
  174. drawText(',', ast.right.x - signOffset, 25)
  175. drawText(')', ast.right.x + ast.right.width + signOffset, 25)
  176. },
  177. })
  178. }
  179. const drawAst = (ast) => {
  180. console.log(astToString(ast))
  181. console.log(ast)
  182. const margin = 50
  183. ast = deepCopy(ast)
  184. setSize(ast)
  185. setPosition(ast, margin, margin)
  186. const [width, height] = [ast.width + margin * 2, ast.height + margin * 2]
  187. graph.width = width
  188. graph.height = height
  189. ctx.clearRect(0, 0, width, height)
  190. ctx.font = '25px Arial'
  191. ctx.textAlign = 'center'
  192. ctx.textBaseline = 'middle'
  193. _drawAst(ast)
  194. drawAstStr(ast)
  195. }
  196. const drawAtom = (ast) => {
  197. console.log(astToString(ast))
  198. }
  199. const showSteps = (res) => {
  200. res.steps.forEach((ast) => {
  201. console.log(astToString(ast))
  202. })
  203. }
  204. return {
  205. drawAst,
  206. drawAtom,
  207. showSteps,
  208. }
  209. }
  210. const drawer = LambdaDrawer('graph')
  211. window._PLAYGROUND = {
  212. instance: drawer,
  213. draw: drawer.drawAst,
  214. }