Как реализовать unquote-splice из lisp в JavaScript
У меня есть базовый интерпретатор lisp, над которым я работаю https://codepen.io/jcubic/pen/gvvzdp?editors=1010
и у меня возникли проблемы со сплайсингом без кавычек и кодом lisp:
(print `(,@(list 1 2 3)))
Структура выходного списка этого кода из синтаксического анализатора выглядит следующим образом:
(<#symbol 'print'> (<#symbol 'quasiquote'> (<#symbol 'unquote-splicing'> <#symbol 'list'> 1 2 3)))
Я получаю вывод как ((1 2 3))
и не (1 2 3)
Я проверил на biwascheme и результат должен быть (1 2 3)
,
console.log в функции печати указывает, что это список, где car
есть (список 1 2 3), а не список сам.
мой макрос для квазицитата выглядит так:
quasiquote: new Macro(function(arg) {
var env = this;
function recur(pair) {
if (pair instanceof Pair) {
if (Symbol.is(pair.car, 'unquote-splicing')) {
// this if is first invoke of recur function for provided code
var eval_pair = evaluate(pair.cdr, env);
if (!eval_pair instanceof Pair) {
throw new Error('Value of unquote-splicing need to be pair')
}
// here eval_pair.car is 1 (first item in list)
return eval_pair;
}
if (Symbol.is(pair.car, 'unquote')) {
return evaluate(pair.cdr, env);
}
var car = pair.car;
if (car instanceof Pair) {
car = recur(car);
}
var cdr = pair.cdr;
if (cdr instanceof Pair) {
cdr = recur(cdr);
}
return new Pair(car, cdr);
}
return pair;
}
return recur(arg);
})
Макрос просто:
function Macro(fn) {
this.fn = fn;
}
Macro.prototype.invoke = function(code, env) {
return this.fn.call(env, code);
};
и моя функция eval выглядит так:
function evaluate(code, env) {
env = env || global_env;
var value;
if (typeof code === 'undefined') {
return;
}
var first = code.car;
if (first instanceof Symbol) {
var rest = code.cdr;
// resolve return value from environment
value = resolve(first, env);
if (value instanceof Macro) {
return value.invoke(rest, env);
} else if (typeof value !== 'function') {
throw new Error('Unknown function `' + first.name + '\'');
} else {
var args = [];
var node = rest;
while(true) {
if (node !== nil) {
args.push(evaluate(node.car, env));
}
if (node.cdr === nil) {
break;
}
node = node.cdr;
}
return value.apply(env, args);
}
} else if (code instanceof Symbol) {
value = resolve(code, env);
if (value === 'undefined') {
throw new Error('Unbound variable `' + code.name + '\'');
}
return value;
} else {
return code;
}
}