PHPerKaigiのコードゴルフ問題として作問した過程でできたLispです。
標準入力で以下のような形式のJSONが入力される。以下の規則に従って処理せよ。出力はecho json_encode(JSON_PRETTY_PRINT)
形式で出力する。
- 入力はJSONのarrayであり、先頭が命令、以降の要素を引数とする
["+", 1, 2, 3]
は、一般的な計算式の1 + 2 + 3
を意味する["-", 1, 2, 3]
は、一般的な計算式の1 - 2 - 3
を意味する["*", 1, 2, 3]
は、一般的な計算式の1 * 2 * 3
を意味する["/", 1, 2, 3]
は、一般的な計算式の1 / 2 / 3
を意味する- 計算結果は全て以上の式をPHPで処理したものとする
["list", ...exprs]
- 引数を処理した結果を配列(リスト)として返す
["list", 1, 2, ["-", 5, 2]]
→[1, 2, 3]
- 以下の式は特別に処理する
"var"
:var
という変数を参照する["quote", expr]
: 引数の式を評価しない["quote", "str"]
: 文字列の"str"
を返す["quote", ["+", 1, 2, "foo"]]
:["+", 1, 2, "foo"]
というリストを返す- TODO:
quote
と文字列の関連がめんどくさいので、たぶん両方とも仕様から消す
["let", [binds], ...body]
- binds:
[["a", 1], ["b", 2]]
→...body
の部分でのみ有効なローカル変数a = 1; b = 2
を束縛する - ...body: 複数の式を順番に評価し、最後の式を結果として返す
["let", [["n", 1], ["m", 2]] ["+", "a", "b"]]
:3
という整数を返す["let", [["n", 1], ["m", 2]] ["list", "a", "b"]]
:[1, 2]
という配列を返す
- binds:
["lambda", [params], ...body]
: 無名関数を作成する["lambda", ["num"], ["*", 2, "n"]]
: 引数を二倍した値を返す関数
-
["funcall", ["lambda", ["num"], ["*", 2, "num"]], 10]
:20
を返す
["map", lambda, list]
: listの要素をlambdaで呼び出した結果の新しいリストを返す["map", ["lambda", ["num"], ["*", 2, "num"]], [1, 2, 3]]
:[2, 4, 6]
を返す
入力:
["+", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
出力:
55
入力:
["let", [["a", 2], ["b", 3]],
["setq", "c", 4],
["/", ["-", ["+", 1, ["*", "a" , "b"]], "c"], 10]]
出力:
0.3
入力:
["let", [["1+", ["lambda", ["n"], ["+", "n", 1]]]],
["map", "1+", ["list", 1, 2, 3, 4, 5]]]
出力:
[
2,
3,
4,
5,
6
]
<?php
if (!defined('STDIN')) {
define('STDIN', fopen('php://memory', 'rw'));
fwrite(STDIN, <<<'EOT'
["+", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
EOT);
/* fwrite(STDIN, <<<'EOT'
["let", [["a", 2], ["b", 3]],
["setq", "c", 4],
["/", ["-", ["+", 1, ["*", "a" , "b"]], "c"], 10]]
EOT);*/
/* fwrite(STDIN, <<<'EOT'
["let", [["1+", ["lambda", ["n"], ["+", "n", 1]]]],
["map", "1+", ["list", 1, 2, 3, 4, 5]]]
EOT);*/
rewind(STDIN);
}
$prog = json_decode(stream_get_contents(STDIN), JSON_THROW_ON_ERROR);
echo json_encode(lisp($prog), JSON_PRETTY_PRINT), "\n";
function lisp(mixed $expr, array &$env = []): mixed
{
//var_dump($expr);
//var_dump(compact('env'));
if (is_int($expr) || is_float($expr)) {
return $expr;
}
if (is_string($expr)) {
return $env[$expr];
}
if ($expr === null) {
return null;
}
$op = array_shift($expr);
//var_dump($op);
switch ($op) {
case "funcall":
$func = $env[array_shift($expr)];
$args = $expr;
if ($func[0] === "lambda") {
[$lambda, $params, $body] = $func;
$new_env = $env;
foreach ($params as $i => $param) {
$new_env[$param] = $args[$i];
}
return lisp($body, $new_env);
}
case "setq":
$name = array_shift($expr);
$result = lisp($expr[0], $env);
$env[$name] = $result;
return $result;
case "quote": return $expr[0];
case "+":
$result = 0;
foreach ($expr as $e) {
$result += lisp($e, $env);
}
return $result;
case "-":
$result = lisp(array_shift($expr), $env);
foreach ($expr as $e) {
$result -= lisp($e, $env);
}
return $result;
case "*":
$result = 1;
foreach ($expr as $e) {
$result *= lisp($e, $env);
}
return $result;
case "/":
$result = lisp(array_shift($expr), $env);
foreach ($expr as $e) {
$result /= lisp($e, $env);
}
return $result;
case "let":
$bind = array_shift($expr);
$new_env = $env;
foreach ($bind as [$name, $value]) {
$new_env[$name] = $value;
}
$result = null;
foreach ($expr as $e) {
$result = lisp($e, $new_env);
}
return $result;
case "list":
$result = [];
foreach ($expr as $e) {
$result[] = lisp($e, $env);
}
return $result;
case "map":
$result = [];
$func = array_shift($expr);
foreach (lisp($expr[0], $env) as $e) {
$result[] = lisp(["funcall", $func, $e], $env);
}
return $result;
default:
var_dump(compact('op', 'expr'));
//var_dump(compact('env'));
}
}