| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- function deepCopy(obj) {
- if (typeof obj === 'object') {
- const newObj = {}
- for (const prop in obj) {
- newObj[prop] = deepCopy(obj[prop])
- }
- return newObj
- } else {
- return obj
- }
- }
- function LambdaDrawer(id) {
- const graph = document.getElementById(id)
- const ctx = graph.getContext('2d')
- const defaultAlign = 'center'
- ctx.font = '25px Arial'
- ctx.textAlign = defaultAlign
- ctx.textBaseline = 'middle'
- // config setting
- const r = 30
- const layerGap = 40
- const signWidth = 10
- // functions
- const isId = (id) => id.id != null
- const isAbs = (abs) => abs.param != null
- const isApp = (app) => app.left != null
- const nodeBottom = (ast) => [ast.cx, ast.cy + r]
- const nodeTop = (ast) => [ast.cx, ast.cy - r]
- const astOperation = (ast, op) => {
- if (isId(ast)) {
- return op.id()
- } else if (isAbs(ast)) {
- return op.abs()
- } else if (isApp(ast)) {
- return op.app()
- } else {
- throw new Error('unknown ast type')
- }
- }
- const astToString = (ast) => {
- return astOperation(ast, {
- id: () => ast.name,
- abs: () => `λ${ast.param.name}.${astToString(ast.body)}`,
- app: () => `(${astToString(ast.left)} ${astToString(ast.right)})`,
- })
- }
- const setSize = (ast) => {
- ast.str = astToString(ast)
- return astOperation(ast, {
- id() {
- // n
- ast.width = 2 * r
- ast.height = 2 * r
- },
- abs() {
- setSize(ast.param)
- setSize(ast.body)
- // \param.(body)
- ast.width = ast.param.width + ast.body.width + 4 * signWidth
- ast.height = ast.body.height + 2 * r + layerGap
- },
- app() {
- // (left,right)
- setSize(ast.left)
- setSize(ast.right)
- ast.width = ast.left.width + ast.right.width + 3 * signWidth
- ast.height =
- Math.max(ast.left.height, ast.right.height) + 2 * r + layerGap
- },
- })
- }
- const setPosition = (ast, x, y) => {
- ast.x = x
- ast.y = y
- return astOperation(ast, {
- id() {
- // n
- ast.cx = x + r
- ast.cy = y + r
- },
- abs() {
- // \param.(body)
- const nextLayerY = y + 2 * r + layerGap
- setPosition(ast.param, x + signWidth, nextLayerY)
- setPosition(ast.body, x + ast.param.width + 3 * signWidth, nextLayerY)
- ast.cx = (ast.param.cx + ast.body.cx) / 2
- ast.cy = y + r
- },
- app() {
- // (left,right)
- const nextLayerY = y + 2 * r + layerGap
- setPosition(ast.left, x + signWidth, nextLayerY)
- setPosition(ast.right, x + ast.left.width + 2 * signWidth, nextLayerY)
- ast.cx = (ast.left.cx + ast.right.cx) / 2
- ast.cy = y + r
- },
- })
- }
- const drawText = (value, cx, cy, align = defaultAlign) => {
- ctx.textAlign = align
- ctx.fillText(value, cx, cy)
- ctx.textAlign = defaultAlign
- }
- const drawLine = (sx, sy, fx, fy) => {
- ctx.beginPath()
- ctx.moveTo(sx, sy)
- ctx.lineTo(fx, fy)
- ctx.stroke()
- }
- const drawNode = (cx, cy, r, value) => {
- ctx.beginPath()
- ctx.arc(cx, cy, r, 0, 2 * Math.PI)
- ctx.stroke()
- ctx.fillText(value, cx, cy)
- }
- const _drawAst = (ast) => {
- astOperation(ast, {
- id() {
- const tag = ast.id >= 0 ? `${ast.name}:${ast.id}` : ast.name
- drawNode(ast.cx, ast.cy, r, tag)
- },
- abs() {
- drawNode(ast.cx, ast.cy, r, 'Abs')
- _drawAst(ast.param)
- _drawAst(ast.body)
- const [sx, sy] = nodeBottom(ast)
- drawLine(sx, sy, ...nodeTop(ast.param))
- drawLine(sx, sy, ...nodeTop(ast.body))
- },
- app() {
- drawNode(ast.cx, ast.cy, r, 'App')
- _drawAst(ast.left)
- _drawAst(ast.right)
- const [sx, sy] = nodeBottom(ast)
- drawLine(sx, sy, ...nodeTop(ast.left))
- drawLine(sx, sy, ...nodeTop(ast.right))
- },
- })
- }
- const drawAstStr = (ast) => {
- const x = ast.cx + r + signWidth / 2
- astOperation(ast, {
- id() {},
- abs() {
- drawText(ast.str, x, ast.cy, 'left')
- drawAstStr(ast.body)
- },
- app() {
- drawText(ast.str, x, ast.cy, 'left')
- drawAstStr(ast.left)
- drawAstStr(ast.right)
- },
- })
- }
- const drawAstInfo = (ast) => {
- astOperation(ast, {
- id() {
- drawText(ast.name, ast.cx, 25)
- },
- abs() {
- drawAstInfo(ast.param)
- drawAstInfo(ast.body)
- const signOffset = signWidth / 2
- drawText('λ', ast.x + signOffset, 25)
- drawText('.', ast.param.cx + r + signOffset, 25)
- drawText('(', ast.body.x - signOffset, 25)
- drawText(')', ast.body.x + ast.body.width + signOffset, 25)
- },
- app() {
- drawAstInfo(ast.left)
- drawAstInfo(ast.right)
- const signOffset = signWidth / 2
- drawText('(', ast.x + signOffset, 25)
- drawText(',', ast.right.x - signOffset, 25)
- drawText(')', ast.right.x + ast.right.width + signOffset, 25)
- },
- })
- }
- const drawAst = (ast) => {
- console.log(astToString(ast))
- console.log(ast)
- const margin = 50
- ast = deepCopy(ast)
- setSize(ast)
- setPosition(ast, margin, margin)
- const [width, height] = [ast.width + margin * 2, ast.height + margin * 2]
- graph.width = width
- graph.height = height
- ctx.clearRect(0, 0, width, height)
- ctx.font = '25px Arial'
- ctx.textAlign = 'center'
- ctx.textBaseline = 'middle'
- _drawAst(ast)
- drawAstStr(ast)
- }
- const drawAtom = (ast) => {
- console.log(astToString(ast))
- }
- const showSteps = (res) => {
- res.steps.forEach((ast) => {
- console.log(astToString(ast))
- })
- }
- return {
- drawAst,
- drawAtom,
- showSteps,
- }
- }
- const drawer = LambdaDrawer('graph')
- window._PLAYGROUND = {
- instance: drawer,
- draw: drawer.drawAst,
- }
|