我正在尝试使用JavaScript作为源语言来实现编程语言LISP的一小部分。当这个小的口齿不清是一个字符串,如下所示:
(define func (lambda (args)(body)))
然后将其传递给名为CreateLambda的函数。 例如,传递以下字符串:
(define add (lambda(x y)(+ x y)))
然后由此函数创建Javascript函数;
function createLambda(expr, env) { // lambda args body
expr.shift(); //remove the "lambda".
var lambdaArgs = expr.shift(); //get the arguments
var lambdaBody = expr.shift(); // get the body
var parentEnvironment = env;
return function() {
var lambdaEnvironment = new environment(parentEnvironment, "lambda environment");
for(var i = 0; i < arguments.length; i++) {
var tempVal = evaluate(arguments[i], parentEnvironment);
lambdaEnvironment.add(lambdaArgs[i], tempVal);
}
return evaluate(lambdaBody, lambdaEnvironment);
}
}
稍后,如果在小型lisp中调用和评估此函数,则类似:
(add 2 3)
在以下情况下,evaluate循环将拾取此字符串:
else if(typeof expr[0] === 'string') {
var lispFunc = env.get(expr[0]); //find the function in the environment.
expr.shift();
var lispFuncArgs = [];
var tempArg = expr.shift();
while(tempArg != null) { //apply the variables
lispFuncArgs.push(evaluate(tempArg, env));
tempArg = expr.shift();
}
return lispFunc.apply(this, lispFuncArgs);
第一次传递给evaluate-function时,它按预期工作,返回数字5.
但是,如果再次调用相同的函数。它仅作为&#34; undefined&#34;进入评估循环。
完整的HTML;
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Script-
Type" content="text/javascript">
<title>LISP in JavaScript</title>
<script type="text/javascript" src="lisp.js"></script>
</head>
<body>
<form id="repl" name="repl" action="parse(prompt.value)">
lisp==>
<input id="prompt" size="200" value="" name="prompt"
maxlength="512">
<br>
<input type=button style="width:60px;height:30px" name="btnEval"
value="eval" onclick="output(prompt.value)">
<br>
</form>
<div id="debugdiv" style="background-
color:orange;width=100px;height=20px">
</div>
</body>
</html>
完整的JavaScript;
//functions for parsing input String
function parse(exp) {
return readFromTokes(tokenize(exp)); //code
}
function isNumeric(arg) {
return !isNaN(arg);
}
function readFromTokes(exp) {
//Create abstract syntax tree
if(exp.length == 0) {
}
var token = exp.shift();
if(token == '(') {
var L = [];
while(exp[0] != ')') {
L.push(readFromTokes(exp));
}
exp.shift(); //remove end paranthesis
return L;
} else {
if(token == ')') {
console.log("Unexpected )");
} else {
return atom(token);
}
}
}
function tokenize(exp) {
//Convert a program in form of a string into an array (list)
var re = /\(/g;
var re2 = /\)/g;
exp = exp.replace(re, " ( ");
exp = exp.replace(re2, " ) ");
exp = exp.replace(/\s+/g, ' ');
exp = exp.trim().split(" ");
return exp;
}
function atom(exp){
if(isNumeric(exp)) {
return parseInt(exp); //A number is a number
} else {
return exp; //Everything else is a symbol
}
}
function environment(parentEnvironment, name) {
var bindings = [];
var parent = parentEnvironment;
var name = name;
function add(variable, value) {
console.log("variable: " + variable + " value: " + value);
bindings.push([variable, value]);
}
function printName() {
console.log(name);
}
function print() {
console.log("printing environment: ")
for(var i = 0; i < bindings.length; i++) {
console.log(bindings[i][0] + " " + bindings[i][1]);
}
}
function get(variable) {
for(var i = 0; i < bindings.length; i++) {
if(variable == bindings[i][0]) {
return bindings[i][1];
}
}
if(parent != null) {
return parent.get(variable);
} else {
console.log("No such variable");
}
}
function getParent(){
return parent;
}
this.add = add;
this.get = get;
this.getParent = getParent;
this.print = print;
this.printName = printName;
return this;
}
function addPrimitives(env) {
env.add("+", function() {
var s = 0;
for(var i = 0; i < arguments.length; i++) {
s += arguments[i];
}
return s
});
env.add("-", function() {
var s = arguments[0];
for(var i = 1; i < arguments.length; i++) {
s -= arguments[i];
}
return s
});
env.add("*", function() {
var s = 1;
for(var i = 0; i < arguments.length; i++){
s *= arguments[i];
}
return s
});
env.add("/", function(x, y) { return x / y });
}
function createLambda(expr, env) { // lambda args body
expr.shift(); //remove the "lambda".
var lambdaArgs = expr.shift(); //get the arguments
var lambdaBody = expr.shift(); // get the body
var parentEnvironment = env;
return function() {
var lambdaEnvironment = new environment(parentEnvironment, "lambda
environment");
for(var i = 0; i < arguments.length; i++) {
var tempVal = evaluate(arguments[i], parentEnvironment);
lambdaEnvironment.add(lambdaArgs[i], tempVal);
}
return evaluate(lambdaBody, lambdaEnvironment);
}
}
function evaluate(expr, env) {
console.log(expr + " has entered evaluate loop");
env.printName();
env.print();
if(typeof expr === 'string') {
console.log(expr + " is a symbol");
return env.get(expr);
} else if(typeof expr === 'number') {
console.log(expr + " is a number");
return expr;
} else if(expr[0] === 'define') { // (define var value)
console.log(expr + " is a define statement");
expr.shift();
var newVar = expr.shift();
var newVal = evaluate(expr.shift(), env);
env.add(newVar, newVal);
return env;
} else if (expr[0] === 'lambda') { // (lambda args body)
console.log(expr + " is a lambda statement");
return createLambda(expr, env);
} else if (typeof expr[0] === 'string'){
var lispFunc = env.get(expr[0]);
expr.shift();
var lispFuncArgs = [];
var tempArg = expr.shift();
while(tempArg != null) {
lispFuncArgs.push(evaluate(tempArg, env));
tempArg = expr.shift();
}
console.log("Function: " + lispFunc);
console.log("Arguments: " + lispFuncArgs);
return lispFunc.apply(this, lispFuncArgs);
} else {
console.log(expr + " cannot be interpreted");
}
}
var globalEnvironment = new environment(null, "Global");
addPrimitives(globalEnvironment);
function start(string) {
return evaluate(parse(string), globalEnvironment);
}
var output = function (string) {
try {
document.getElementById('debugdiv').innerHTML = start(string);
} catch(e) {
document.getElementById('debugdiv').innerHTML = e.name + ': ' + e.message;
}
};
答案 0 :(得分:2)
这是因为您的代码中存在一个小错误。
当你在第134行调用evaluate(lambdaBody, lambdaEnvironment)
时,你提供lambdaBody
作为第一个参数,这是&#39;函数中的expr
参数evaluate(expr,env)&# 39;功能(行:138)。在评估期间,您使用tempArg = expr.shift();
函数使用它,并在其为空时退出循环。第二次,您的lambdaBody
很遗憾是一个空数组。
要修复它,您应该更改evaluate函数而不是修改其参数(例如,创建副本并使用副本)
我建议您修复评估功能中的错误,但真正有用的示例是将第134行更改为深层复制 lambdaBody
,如下所示:
evaluate(lambdaBody.slice(0), lambdaEnvironment);
无论你多少次打电话,你都会看到它现在有效。但是bug实际上是在evaluate函数中,所以在那里修复它。