Skip to content

Instantly share code, notes, and snippets.

@zonuexe
Created June 15, 2025 16:32
Show Gist options
  • Save zonuexe/31b33ae7d0f9285eab08e59145734c9f to your computer and use it in GitHub Desktop.
Save zonuexe/31b33ae7d0f9285eab08e59145734c9f to your computer and use it in GitHub Desktop.
PHPerKaigi Lisp

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] という配列を返す
    • ["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'));
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment