[obsidian] vault backup: 2023-11-25 00:30:28[
This commit is contained in:
		
							
								
								
									
										117
									
								
								content/Node.jsで100行で書くLISP風ラムダ計算.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								content/Node.jsで100行で書くLISP風ラムダ計算.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,117 @@
 | 
				
			|||||||
 | 
					#programming-language #compiler-design #lisp 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[Make A Lisp](https://github.com/kanaka/mal)よりさらに最小限の実装ってどのくらいかなー、というのを考えた。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					クロージャも作れるのでこのくらいのことはできる。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```lisp
 | 
				
			||||||
 | 
					(let double (lambda (x) (* x x)) (double 6)) 
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					まあプリミティブ演算は四則演算だけで再帰関数は自力で定義
 | 
				
			||||||
 | 
					```lisp
 | 
				
			||||||
 | 
					(let fix (lambda (f) ((lambda (x) (f (lambda (y) (x x y) ))) ())  ) )
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```js
 | 
				
			||||||
 | 
					const reader = require("readline").createInterface({
 | 
				
			||||||
 | 
					    input: process.stdin,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const tokenize = (str) =>
 | 
				
			||||||
 | 
					    str.match(/[\(]|[\)]|[\+\-\*\/]|[a-zA-Z_]+|(-?\d+(\.\d+)?)/g);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const parse_token = (token, stack, res) => {
 | 
				
			||||||
 | 
					    switch (token) {
 | 
				
			||||||
 | 
					        case '(': {
 | 
				
			||||||
 | 
					            let child = [];
 | 
				
			||||||
 | 
					            stack.push(child);
 | 
				
			||||||
 | 
					            return child;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        case ')': {
 | 
				
			||||||
 | 
					            let c = stack.pop();
 | 
				
			||||||
 | 
					            if (stack.length == 0) {
 | 
				
			||||||
 | 
					                return c
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                stack[stack.length - 1].push(c)
 | 
				
			||||||
 | 
					                return stack[stack.length - 1]
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        default: {
 | 
				
			||||||
 | 
					            res.push(token);
 | 
				
			||||||
 | 
					            return res
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const parse = (tokens) => {
 | 
				
			||||||
 | 
					    let stack = [];
 | 
				
			||||||
 | 
					    let next_res = [];
 | 
				
			||||||
 | 
					    for (token of tokens) {
 | 
				
			||||||
 | 
					        next_res = parse_token(token, stack, next_res)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return next_res
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const read = (str) => {
 | 
				
			||||||
 | 
					    const res = parse(tokenize(str));
 | 
				
			||||||
 | 
					    return res
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const binop = (args, fn) => fn(args[0], args[1]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const env = {
 | 
				
			||||||
 | 
					    "parent": null,
 | 
				
			||||||
 | 
					    "+": args => binop(args, (a, b) => a + b),
 | 
				
			||||||
 | 
					    "-": args => binop(args, (a, b) => a - b),
 | 
				
			||||||
 | 
					    "*": args => binop(args, (a, b) => a * b),
 | 
				
			||||||
 | 
					    "/": args => binop(args, (a, b) => a / b)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const try_find = (name, env) => {
 | 
				
			||||||
 | 
					    if (env)
 | 
				
			||||||
 | 
					        if (env[name]) {
 | 
				
			||||||
 | 
					            return env[name]
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            return try_find(name, env["parent"])
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					        return null
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					const eval = (expr, env) => {
 | 
				
			||||||
 | 
					    if (!Array.isArray(expr)) {
 | 
				
			||||||
 | 
					        return !isNaN(parseFloat(expr)) ? parseFloat(expr) : try_find(expr, env)
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        const op = expr.shift();
 | 
				
			||||||
 | 
					        switch (op) {
 | 
				
			||||||
 | 
					            case 'let': {
 | 
				
			||||||
 | 
					                let newenv = { "parent": env };
 | 
				
			||||||
 | 
					                env[expr[0]] = eval(expr[1], newenv);
 | 
				
			||||||
 | 
					                return eval(expr[2], env)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case 'lambda': return ['closure', expr[0], expr[1], env]//ids,body,env
 | 
				
			||||||
 | 
					            default: {
 | 
				
			||||||
 | 
					                let fn = try_find(op, env);
 | 
				
			||||||
 | 
					                if (fn) {
 | 
				
			||||||
 | 
					                    if (Array.isArray(fn) && fn[0] === "closure") {
 | 
				
			||||||
 | 
					                        for (let i = 0; i < fn[1].length; i++) {
 | 
				
			||||||
 | 
					                             fn[3][fn[1][i]] = expr[i]
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        return eval(fn[2],  fn[3])
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        return fn(expr.map(e => eval(e, env)))
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    console.error(`symbol ${op} not found`)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					const rep = (line) => {
 | 
				
			||||||
 | 
					    console.log(eval(read(line), env))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					reader.on("line", (line) => {
 | 
				
			||||||
 | 
					    if (line) { rep(line); }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
@@ -1,5 +1,7 @@
 | 
				
			|||||||
#programming-language  #compiler-design 
 | 
					#programming-language  #compiler-design 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[Node.jsで100行で書くLISP風ラムダ計算]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[Write a JavaScript Parser in Rust](https://oxc-project.github.io/javascript-parser-in-rust/ja/docs/intro/)
 | 
					[Write a JavaScript Parser in Rust](https://oxc-project.github.io/javascript-parser-in-rust/ja/docs/intro/)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user