Skip to content

Instantly share code, notes, and snippets.

@kmizu
Created October 24, 2024 09:12
Show Gist options
  • Save kmizu/1bea87223f942f4dad47e174f1325251 to your computer and use it in GitHub Desktop.
Save kmizu/1bea87223f942f4dad47e174f1325251 to your computer and use it in GitHub Desktop.
インタプリタの実装からプログラムを導出する
Here is my program lang. Could you provide number guessing game in the lang?
const readline = require('readline');
const fs = require('fs');
const readLineSync = require('readline-sync');
function input(prompt) {
return readLineSync.question(prompt);
}
//プログラム全体
class Program {
constructor(defs, ...expressions){
this.defs = defs;
this.expressions = expressions;
}
}
//関数定義を表すクラス
class FunDef {
constructor(name, args, body) {
this.name = name;
this.args = args;
this.body = body;
}
}
// 式を表すクラス
class Expression {}
// 代入を表すクラス
class Assignment extends Expression {
constructor(name, expr) {
super();
this.name = name;
this.expr = expr;
}
}
// 二項演算子(+、-、*、/)のためのクラス
class BinExpr extends Expression {
constructor(operator, lhs, rhs) {
super();
this.operator = operator;
this.lhs = lhs;
this.rhs = rhs;
}
}
// 単項演算子(not)のためのクラス
class UnaryExpr extends Expression {
constructor(operator, expr) {
super();
this.operator = operator;
this.expr = expr;
}
}
// 整数値のためのクラス
class Num extends Expression {
constructor(value) {
super();
this.value = value;
}
}
// 真偽値のためのクラス
class Bool extends Expression {
constructor(value) {
super();
this.value = value;
}
}
// 文字列のためのクラス
class Str extends Expression {
constructor(value) {
super();
this.value = value;
}
}
// 変数参照のためのクラス
class VarRef extends Expression {
constructor(name){
super();
this.name = name;
}
}
// 関数呼び出しのためのクラス
class FunCall extends Expression {
constructor(name, ...args) {
super();
this.name = name;
this.args = args;
}
}
// 条件分岐のためのクラス
class If extends Expression {
constructor(cond, thenExpr, elseExpr) {
super();
this.cond = cond;
this.thenExpr = thenExpr;
this.elseExpr = elseExpr;
}
}
// 繰り返しのためのクラス
class While extends Expression {
constructor(cond, body) {
super();
this.cond = cond;
this.body = body;
}
}
// 連接のためのクラス
class Seq extends Expression {
constructor(...bodies) {
super();
this.bodies = bodies;
}
}
// 組み込み関数のためのクラス
class BuiltinFun {
constructor(name, func) {
this.name = name;
this.func = func;
}
}
function translateExpr(jsonObject) {
if(Array.isArray(jsonObject)) {
const operator = jsonObject[0];
switch(operator) {
case "+":
return new BinExpr("+", translateExpr(jsonObject[1]), translateExpr(jsonObject[2]));
case "-":
return new BinExpr("-", translateExpr(jsonObject[1]), translateExpr(jsonObject[2]));
case "*":
return new BinExpr("*", translateExpr(jsonObject[1]), translateExpr(jsonObject[2]));
case "/":
return new BinExpr("/", translateExpr(jsonObject[1]), translateExpr(jsonObject[2]));
case "%":
return new BinExpr("%", translateExpr(jsonObject[1]), translateExpr(jsonObject[2]));
case "<":
return new BinExpr("<", translateExpr(jsonObject[1]), translateExpr(jsonObject[2]));
case ">":
return new BinExpr(">", translateExpr(jsonObject[1]), translateExpr(jsonObject[2]));
case "<=":
return new BinExpr("<=", translateExpr(jsonObject[1]), translateExpr(jsonObject[2]));
case ">=":
return new BinExpr(">=", translateExpr(jsonObject[1]), translateExpr(jsonObject[2]));
case "==":
return new BinExpr("==", translateExpr(jsonObject[1]), translateExpr(jsonObject[2]));
case "!=":
return new BinExpr("!=", translateExpr(jsonObject[1]), translateExpr(jsonObject[2]));
case "and":
return new BinExpr("and", translateExpr(jsonObject[1]), translateExpr(jsonObject[2]));
case "or":
return new BinExpr("or", translateExpr(jsonObject[1]), translateExpr(jsonObject[2]));
case "not":
return new UnaryExpr("not", translateExpr(jsonObject[1]));
case "seq":
return new Seq(...jsonObject.slice(1).map(translateExpr));
case "if":
return new If(translateExpr(jsonObject[1]), translateExpr(jsonObject[2]), translateExpr(jsonObject[3]));
case "while":
return new While(translateExpr(jsonObject[1]), translateExpr(jsonObject[2]));
case "<-":
return new Assignment(jsonObject[1], translateExpr(jsonObject[2]));
case "ref":
return new VarRef(jsonObject[1]);
case "call":
return new FunCall(jsonObject[1], ...jsonObject.slice(2).map(translateExpr));
}
}
switch (typeof jsonObject) {
case "number":
return new Num(jsonObject);
case "string":
return new Str(jsonObject);
case "boolean":
return new Bool(jsonObject);
}
throw new Error("Not implemented for: " + JSON.stringify(jsonObject));
}
/*
* {"functions": [
* ["add", ["x", "y"] , ["+", x, y]], ...
* ],
* "body": ["call", 1, 2]
* }
*/
function translateProgram(jsonProgram) {
const defs = jsonProgram['functions'].map(f =>
new FunDef(f[0], f[1], translateExpr(f[2]))
);
const body = translateExpr(jsonProgram['body']);
return new Program(defs, body);
}
function evalProgram(program) {
const env = {};
const funEnv = {};
const builtinFunEnv = {
'print': new BuiltinFun('print', (args) => {
console.log(...args);
return args[0];
}),
'input': new BuiltinFun('input', (prompt) => {
return input(prompt);
}),
'parseInt': new BuiltinFun('parseInt', (a) => {
return parseInt(a)
}),
'add': new BuiltinFun('add', (args) => args.reduce((a, b) => a + b, 0)),
'mul': new BuiltinFun('mul', (args) => args.reduce((a, b) => a * b, 1)),
'length': new BuiltinFun('length', (args) => args[0].length),
'chatAt': new BuiltinFun('chatAt', (args) => args[0].charAt(args[1])),
'slice': new BuiltinFun('slice', (args) => args[0].slice(args[1], args[2])),
};
let result = null;
program.defs.forEach((d) => {
funEnv[d.name] = d;
});
program.expressions.forEach((e) => {
result = eval(e, env, funEnv, builtinFunEnv);
});
return result;
}
function evalJsonProgram(jsonString) {
const jsonProgram = JSON.parse(jsonString);
const program = translateProgram(jsonProgram);
return evalProgram(program);
}
function eval(expr, env, funEnv, builtinFunEnv) {
if(expr instanceof BinExpr) {
const resultL = eval(expr.lhs, env, funEnv, builtinFunEnv);
const resultR = eval(expr.rhs, env, funEnv, builtinFunEnv);
switch(expr.operator) {
case "+":
return resultL + resultR;
case "-":
return resultL - resultR;
case "*":
return resultL * resultR;
case "/":
return resultL / resultR;
case "%":
return resultL % resultR;
case "<":
return resultL < resultR;
case ">":
return resultL > resultR;
case "<=":
return resultL <= resultR;
case ">=":
return resultL >= resultR;
case "==":
return resultL === resultR;
case "!=":
return resultL !== resultR;
case "and":
return resultL && resultR;
case "or":
return resultL || resultR;
}
} else if(expr instanceof UnaryExpr) {
const result = eval(expr.expr, env, funEnv, builtinFunEnv);
switch(expr.operator) {
case "not":
return !result;
}
} else if(expr instanceof Num) {
return expr.value;
} else if(expr instanceof Bool) {
return expr.value;
} else if(expr instanceof Str) {
return expr.value;
} else if(expr instanceof VarRef) {
if (!(expr.name in env)) {
throw new Error(variable ${expr.name} is not defined);
}
return env[expr.name];
} else if(expr instanceof Assignment) {
const result = eval(expr.expr, env, funEnv, builtinFunEnv);
env[expr.name] = result;
return result;
} else if(expr instanceof Seq) {
let result = null;
expr.bodies.forEach((e) => {
result = eval(e, env, funEnv, builtinFunEnv);
});
return result;
} else if(expr instanceof If) {
if(eval(expr.cond, env, funEnv, builtinFunEnv)) {
return eval(expr.thenExpr, env, funEnv, builtinFunEnv);
}else {
return eval(expr.elseExpr, env, funEnv, builtinFunEnv);
}
} else if(expr instanceof While) {
while(eval(expr.cond, env, funEnv, builtinFunEnv)) {
eval(expr.body, env, funEnv, builtinFunEnv);
}
return 0;
} else if(expr instanceof FunCall) {
const def = funEnv[expr.name] || builtinFunEnv[expr.name];
if(!def) throw function ${expr.name} is not defined;
const args = expr.args.map((a) => eval(a, env, funEnv, builtinFunEnv));
if (def instanceof BuiltinFun) {
return def.func(args);
}
const newEnv = {}
for(let i = 0; i < def.args.length; i++) {
newEnv[def.args[i]] = args[i];
}
return eval(def.body, newEnv, funEnv, builtinFunEnv);
} else {
console.assert(false, should not reach here ${expr});
}
}
try {
const program = fs.readFileSync(process.argv[2], 'utf8');
evalJsonProgram(program);
} catch (err) {
console.error('Error reading the file:', err);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment