The first thing to explain:
"Acme of Beauty" is not to say the month of this article, because I was not arrogant to this extent: P, it describes the combination of Lisp and JavaScript beautiful form.
Originally the following content is to be in no excellent starting, but unfortunately completed the day of the article suddenly found no gifted "play", until the end of the weekend to restore =.=, because can not wait so long, so first put to the moon in Csdn on the blog.
As the title describes, the following is a technique for implementing a lisp-like language using JavaScript, but the focus is not on how to implement a programming language, but on the simplicity and agility of JavaScript and the beauty of Lisp through the process of thinking and implementation.
Maybe there aren't many people here who are in touch with Lisp, so a lot of people will be surprised by the content or form, if you haven't touched it, don't be surprised, Lisp is really different from all the programming languages you've seen before, because, uh, it's Lisp, Unique Lisp, a graceful, concise, complete , independent of the wonderful thought, perhaps you will find it difficult to understand, but once you understand, you will like it.
All right, let's start our Lispscript tour.
Recently on the Internet to see an article, said JavaScript = C+lisp, so think about the problem, since JavaScript contains some of the lisp of the lineage, then use JavaScript to implement a Lisp-like AI script what will it look like?
As a "functional" language family, the LISt processing has conquered many researchers and enthusiasts with its simple and graceful style and concise and efficient structure since the birth date.
This ancient language and grammar is still used by many people and love, but also in the field of artificial intelligence plays a very large role.
I think that the flexibility of JavaScript plus the simplicity of Lisp should be able to create a very beautiful language, but what is the language like? I'm sure you would like to know, so let's look at this very appealing question together.
(before carefully reading the following, we suggest that you should pour a cup of hot tea, sit down and calm down, take a deep breath, and concentrate on the spirit, because the following process will be interesting and very consuming brain cells ... ^^)
Before going into the Lisp kingdom, let's start with some javascrip preparations ... Please 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]); Recursive read down
if (Code[i] instanceof Function)//Parse expression
{
if (code[i].length <= 0)//parameterless function can omit [] directly called by function name
{
Code[i] = Code[i].call (null);
}
else if (i = = 0)//Call 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 dozens of-line JavaScript code consists of three auxiliary functions, a principal object, a constant nil (which we know later to represent an empty table or logical false), and a stack that holds the name of the function.
The Lispscript static object forms the body of the Lispscript parser, which has only one run method, which parses the passed in Lispscript code in a downward recursive manner, the type of code-a reader who believes that careful readers have found The direct use is an array of JavaScript, a series of "[", "]" and "delimiter", "sequences".
With JavaScript's natural array features, our parser can be designed to be very concise-without splitting and parsing each token, so a short to less than 50 line of code is amazingly fulfilling the core of the entire LISPSCRIPT parser!
The function of three auxiliary functions is to provide parsing for function iterations (toevalstring), Detect sequence anomalies (Assert, not actually used in subsequent implementations), and parse instruction words (Element)
Next we define the expression. An expression or an atom [atom], which is an alphabetic sequence (such as Foo) or a table (list) consisting of 0 or more expressions, separated by commas, and placed in a pair of brackets. Here are some of the expressions:
(Note: An expression of the original Lisp syntax is separated by a space and placed in a pair of parentheses.) Because it's a JavaScript implementation, it's simpler with parentheses and commas.
Foo
[]
[Foo]
[Foo,bar]
[A,b,[c],d]
The last expression is a table of four elements, and the third element itself is a table consisting of one element.
In arithmetic the expression 1 + 1 draws a value of 2. The correct Lisp expression also has a value. If the expression e gets the value V, we say e returns v. Next we'll define several expressions and their return values.
If an expression is a table, we call the first element the operator, and the rest of the elements as arguments. We will define seven primitive (in the sense of axiom) operators: Quote,atom,eq,car,cdr,cons, and cond.
[Quote,x] returns x. We denoted [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 the Atom true if the value of X is an atom or an empty table, otherwise return []. In Lisp we routinely use atomic true to represent true, whereas empty tables represent false.
> [Atom,[_,a]]
True
> [Atom,[_,[a,b,c]]]
[]
> [atom,[_,[]]]
True
Atom = function (ARG)
{
var tmp = Lispscript.run (ARG); Evaluate the parameters first
if (!) ( TMP instanceof Array) | | Tmp.length <= 0)
return true;
Else
return [];
};
Now that we have an operator with an argument that requires a value, we can look at the role of quote. by referencing (quote) a table, we avoid it being evaluated. An unreferenced table passed as an argument to an operator like Atom will be treated as code:
> [Atom,[atom,[_,a]]]
True
Conversely, a referenced table is treated as a table, in this case, a table with two elements:
> [Atom,[_,[atom,[_,a]]]]
[]
This is consistent with the way we use quotes in English. Cambridge (Cambridge) is a town of 90000 people in Massachusetts. and "Cambridge" is a word consisting of 9 letters.
The reference may seem a little strange because very 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 the quote operator to differentiate them.
[Eq,x,y] returns T if the value of x and Y is the same atom or both are empty tables.
> [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 parameters 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] expects the value of X to be a table and returns the first element of X.
> [Car,[_,[a b c]]]
A
Car = function (ARG)
{
var tmp = Lispscript.run (ARG); Evaluate the parameters first
if (tmp instanceof Array && tmp.length > 0)
return tmp[0];
Else
return [];
};
[Cdr,x] expects the value of X to be a table and to 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 parameters first
if (tmp instanceof Array && tmp.length > 0)
return Tmp.slice (1);
Else
return [];
};
[Cons,x,y] expects the value of Y to be a table and returns a new table whose first element is the value of x, followed by the elements of the value 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 parameters 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 sequentially until there is a return of T. If you can find such a P expression, the value of the corresponding e-expression is 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 parameters first
alert (cond);
if (cond = = True && arguments[i][1]!= null)
Return Lispscript.run (arguments[i][1]);
}
}
return [];
};
When an expression begins with five of the seven original operators, its arguments always require a value of. 2 We call such an operator a function.
Then we define a notation to describe the function. The function is represented as [lambda, [...], E], where ... is an atom (called an argument) and E is an expression. If the first element form of an expression
[[Lambda,[...],e],...]
is called a function call. Its value is computed as follows. Each expression is evaluated first, and then E is evaluated. In the evaluation of E, each value appearing 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 an atom and F is not the original operator
[F ...]
and the value of f is a function [lambda,[...]], the value of the expression above is
[[Lambda,[...],e],...]
The value. In other words, parameters can be used not only as arguments but also as operators in an expression:
> [[lambda,[f],[f,[_,[b,c]]],[_,[lambda,[x],[cons,[_,a],x]]]
[A,b,c]
There is another function notation that allows the function to refer to itself, so that we can easily define recursive functions. mark
[Label,f,[lambda,[...],e]]
Represents a function like [lambda,[...],e], plus the attribute: Any F that appears in E is evaluated for this label expression, as if f is an argument to this function.
Suppose we want to define a function [Subst,x,y,z], which takes expression x, Atom Y, and table z as arguments, and returns a table like Z, but the y (at any nesting level) in Z is replaced by X.
> [subst,[_,m],[_,b],[_,[a,b,[a,b,c],d]]]
[A,m,[a,m,c],d]
Current 1/6 page
123456 Next read the full text