First, we need to explain:
"The beauty of perfection" does not mean this article of yueer, because I have not been arrogant to this level: P, which describes the beautiful form of combining Lisp with javascript.
Originally, the following content was intended to be launched in the non-excellent market, but on the day that the article was unfortunately completed, it was suddenly found that the non-excellent market was not restored until last weekend =. =, because it cannot be waited for so long, we first put it in our blog on CSDN.
As described in the title, the following describes the skills for implementing Lisp-like languages using javascript, but the focus is not on how to implement a programming language, it is to demonstrate the simplicity and flexibility of javascript and the beauty of Lisp through thinking and implementation.
Perhaps there are not many people who are familiar with Lisp, so many may be surprised by the following content or forms. If you have never touched it, don't be too surprised, lisp is indeed different from all the programming languages you have seen before, because, er, it is a unique Lisp, a elegant, concise, complete, and independent wonderful idea, you may think it is hard to understand, but once you understand it, you will like it.
Now, let's start our LispScript tour ~
I recently accidentally saw an article on the internet saying javascript = C + Lisp, So I thought about this problem. Since javascript contains part of The Lisp lineage, so what does it look like to use javascript to implement an AI script similar to Lisp?
As a functional language, LISt Processing has conquered many researchers and enthusiasts with its simple and elegant style and simple and efficient structure since its birth.
At present, this old language and grammar are still used and loved by many people, and play a very huge role in AI and other fields.
I think that the flexibility of javascript, coupled with the simplicity of Lisp, should be able to create a very beautiful language, but what does it look like? I believe everyone would like to know, so let's take a look at this very attractive question.
(Before carefully reading the following content, we recommend that you first pour a cup of hot tea, sit down and calm yourself down, take a deep breath, and concentrate on it, because the following process will be interesting and consuming brain cells... ^)
Before entering the Lisp realm, let's prepare for the commit crip... read the following code carefully.
NIL = [];
Array. prototype. toEvalString = function ()
{
If (this. length <= 0) return "NIL ";
Var str = "";
For (var I = 0; I <this. length; I ++)
{
If (this [I] instanceof Array)
Str + = "," + this [I]. toEvalString ();
Else str + = "," + this [I];
}
Return "[" + str. slice (1) + "]";
};
(Function (){
LispScript = {
Run: run
};
Function run (code)
{
If (code instanceof Array)
{
Var elements = new Array ();
For (var I = 0; I <code. length; I ++)
{
Code [I] = run (code [I]); // recursively reads down
If (code [I] instanceof Function) // parse the expression
{
If (code [I]. length <= 0) // a function without any parameters can be omitted [] and called directly by function name
{
Code [I] = code [I]. call (null );
}
Else if (I = 0) // call a function with parameters [funcall, args...]
{
Return code [I]. apply (null, code. slice (1 ));
}
}
}
Return code;
}
Return Element (code );
};
})();
Function Assert (msg, cond)
{
If (cond)
Return true;
Else
{
Alert (msg );
Throw new Error (msg );
}
};
Function Element (arg)
{
If (arg = null)
Return [];
Else if (arg instanceof Function & arg. length <= 0)
Return arg. call (null );
Else
Return arg;
};
_ FunList = new Array ();
The above simple javascript code that does not exceed dozens of lines consists of three auxiliary functions, a subject object, and a constant NIL (we will know that it represents an empty table or logic false ), and a stack that stores the function name.
LispScript static objects constitute the main body of the LispScript parser, which has only one Run method. This method uses a recursive method to parse the LispScript Code passed in, the type of the code-I believe that careful readers have discovered-the javascript array is used directly, that is, a series of [,], and separator.
With the natural array feature of javascript, our parser can be designed very concisely-no need to split and parse Every token, as a result, less than 50 lines of code have dramatically achieved the core of the entire LispScript parser!
The functions of the three auxiliary functions are to provide resolution (toEvalString) for Function Iteration, and to detect sequence exceptions (Assert, which is not actually used in subsequent implementations ), and parse command words (Element)
Next we will first define the expression. an expression is an atomic [atom]. It is a letter sequence (such as foo), or a table (list) composed of zero or multiple expressions. Expressions are separated by commas, put in a pair of brackets. the following are some expressions:
(Note: The expressions in the original Lisp syntax are separated by spaces and placed in a pair of parentheses. Because javascript is implemented, brackets and commas are more concise)
Foo
[]
[Foo]
[Foo, bar]
[A, B, [c], d]
The last expression is a table composed of four elements. The third element is a table composed of one element.
In arithmetic, expression 1 + 1 obtains the value 2. the correct Lisp expression also has a value. if expression e gets the value v, we say e returns v. next, we will define several expressions and their return values.
If an expression is a table, we call the first element as an operator, and the remaining elements as independent variables. we will define seven primitive operators (in the sense of justice): quote, atom, eq, car, cdr, cons, and cond.
[Quote, x] returns x. We can describe [quote, x] as [_, x].
> [Quote, a]
A
> [_, A]
A
> [Quote, [a B c]
[A, B, c]
Quote = _ = function (args)
{
If (arguments. length <1)
Return [];
Else if (arguments. length> = 1)
{
Return arguments [0];
}
};
[Atom, x] returns atomic true. If the value of x is an atom or an empty table, otherwise, [] is returned. in Lisp, we use atomic true to represent truth, while empty table to show false.
> [Atom, [_, a]
True
> [Atom, [_, [a, B, c]
[]
> [Atom, [_, []
True
Atom = function (arg)
{
Var tmp = LispScript. Run (arg); // evaluate the parameter first
If (! (Tmp instanceof Array) | tmp. length <= 0)
Return true;
Else
Return [];
};
Now that an independent variable requires a value operator, let's take a look at the role of quote. by referencing a table, we prevent it from being evaluated. an unreferenced table is passed as an independent variable to an operator like atom and is considered as code:
> [Atom, [atom, [_, a]
True
On the contrary, a referenced table is only regarded as a table. In this example, there are two elements in the table:
> [Atom, [_, [atom, [_, a]
[]
This is consistent with the way we use quotation marks in English. Cambridge (Cambridge) is a town of 90000 people located in the Massachusetts region. "Cambridge" is a word consisting of 9 letters.
References may seem a bit strange because few other languages have similar concepts. it is closely related to the most distinctive features of Lisp: code and data are composed of the same data structure, and we use quote operators to distinguish them.
[Eq, x, y] returns t. If the values of x and y are the same atom or empty tables, otherwise [] is returned.
> [Eq, [_, a], [_, a]
True
> [Eq, [_, a], [_, B]
[]
> [Eq, [_, [], [_, []
True
Equal = eq = function (arg1, arg2)
{
Var tmp1 = LispScript. Run (arg1 );
Var tmp2 = LispScript. Run (arg2); // evaluate the parameter first
If (! (Tmp1 instanceof Array )&&! (Tmp2 instanceof Array )&&
Tmp1.toString () = tmp2.toString () |
(Tmp1 instanceof Function) & (tmp2 instanceof Function) & tmp1.toString () = tmp2.toString () |
(Tmp1 instanceof Array) & (tmp2 instanceof Array) & (tmp1.length = 0) & (tmp2.length = 0 ))
Return true;
Else
Return [];
};
[Car, x] Expect the value of x to be a table and return the first element of x.
> [Car, [_, [a B c]
A
Car = function (arg)
{
Var tmp = LispScript. Run (arg); // evaluate the parameter first
If (tmp instanceof Array & tmp. length> 0)
Return tmp [0];
Else
Return [];
};
[Cdr, x] Expect the value of x to be a table and return all elements after the first element of x.
> [Cdr, [_, [a B c]
[B, c]
Cdr = function (arg)
{
Var tmp = LispScript. Run (arg); // evaluate the parameter first
If (tmp instanceof Array & tmp. length> 0)
Return tmp. slice (1 );
Else
Return [];
};
[Cons, x, y] expect y to be a table and return a new table. Its first element is the value of x, followed by the values of y.
> [Cons, [_, a], [_, [B, c]
[A, B, c]
> [Cons, [_, a], [cons, [_, B], [cons, [_, c], [_, []
[A, B, c]
> [Car, [cons, [_, a], [_, [B c]
A
> [Cdr, [cons, [_, a], [_, [B, c]
[B, c]
Cons = function (arg1, arg2)
{
Var tmp1 = LispScript. Run (arg1 );
Var tmp2 = LispScript. Run (arg2); // evaluate the parameter first
If (tmp2 instanceof Array)
{
Var list = new Array ();
List. push (tmp1 );
Return list. concat (tmp2 );
}
Else
Return [];
};
[Cond [...]... [...] the evaluation rules are as follows. the p expression is evaluated in sequence until one returns t. if such a p expression can be found, the value of the corresponding e expression serves as the return value of the entire cond expression.
> [Cond, [[eq, [_, a], [_, B], [_, first],
[, [Atom, [_, a], [_, second]
Second
Cond = function (args)
{
For (var I = 0; I <arguments. length; I ++)
{
If (arguments [I] instanceof Array)
{
Var cond = LispScript. Run (arguments [I] [0]); // evaluate the parameter first
// Alert (cond );
If (cond = true & arguments [I] [1]! = Null)
Return LispScript. Run (arguments [I] [1]);
}
}
Return [];
};
When an expression starts with five of the seven original operators, its independent variables always require values. 2 we call such operators as functions.
Then we define a mark to describe the function. the function is represented as [lambda, [...], e], where... it is an atomic (called a parameter), and e is an expression. if the first element form of the expression is as follows:
[[Lambda, [...], e],...]
It is called a function call. the value is calculated as follows. evaluate each expression first, and then e. in the evaluate process of e, each value that appears in e is the corresponding value in the last function call.
> [[Lambda, ['X'], [cons, 'x', [_, [c], [_, a]
[A, c]
> [[Lambda, ['x', 'y'], [cons, 'x', [cdr, 'y'], [_, z], [_, [a, B, c]
[Z, B, c]
Lambda = function (args, code)
{
If (code instanceof Array)
{
Var fun = new Function (args,
"For (var I = 0; I <arguments. length; I ++) arguments [I] = LispScript. run (arguments [I]); return LispScript. run ("+ code. toEvalString () + ");");
Var globalFuncName = _ funList. pop ();
Fun. _ funName = globalFuncName;
If (globalFuncName! = Null)
Self [globalFuncName] = fun;
Return fun;
}
Return [];
};
If the first element f of an expression is atomic and f is not the original operator
[F...]
And the value of f is a function [lambda, [...], the value of the above expression is
[[Lambda, [...], e],...]
In other words, a parameter can be used not only as an independent variable but also as an operator in an expression:
> [[Lambda, [f], [f, [_, [B, c], [_, [lambda, [x], [cons ,[_, a], x]
[A, B, c]
There is another function mark that allows the function to mention itself, so that we can easily define recursive functions.
[Label, f, [lambda, [...], e]
Represents an image like [lambda, [...], e] for such a function, add such a feature: Any f that appears in e will evaluate this label expression, as if f is a parameter of this function.
Suppose we want to define the function [subst, x, y, z], which takes the expression x, atomic y, and table z as parameters and returns a table like z, however, y (in any nested hierarchy) in z is replaced by x.
> [Subst, [_, m], [_, B], [_, [a, B, [a, B, c], d]
[A, m, [a, m, c], d]