From 31acf28b38afaedb45ef26b3f5b2517634a5ba35 Mon Sep 17 00:00:00 2001 From: Sami Samhuri Date: Sat, 5 Dec 2009 12:58:57 -0800 Subject: [PATCH] Initial commit. Just a basic Lisp parser so far. --- el.js | 29 ++++++ el/parse.js | 270 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 299 insertions(+) create mode 100644 el.js create mode 100644 el/parse.js diff --git a/el.js b/el.js new file mode 100644 index 0000000..03b5280 --- /dev/null +++ b/el.js @@ -0,0 +1,29 @@ +/* + * Emacs Lisp implementation in JavaScript. + * + * Copyright (c) 2009 Sami Samhuri - sami.samhuri@gmail.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +function EL() {} +/* EL.nil is implicitly undefined */ +EL.t = true; + diff --git a/el/parse.js b/el/parse.js new file mode 100644 index 0000000..0b4b276 --- /dev/null +++ b/el/parse.js @@ -0,0 +1,270 @@ +var EL = function(){}; + +/* + * Emacs Lisp implementation in JavaScript. + * + * Copyright (c) 2009 Sami Samhuri - sami.samhuri@gmail.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +//var EL = function(){}; + +/* EL.nil is implicitly undefined */ +EL.t = true; + +EL.Parser = function(data) { + this.data = data; +}; + +EL.Parser.Error = function(name, message) { + this.parserError = function() {return true;}; + this.name = name; + this.message = message; +}; + +EL.Parser.Error.messages = { + 'eof': "no more input" +}; + +EL.Parser.prototype.error = function(name) { + return new EL.Parser.Error(name, EL.Parser.Error.messages[name]); +}; + +EL.Parser.prototype.peek = function() { + return this.data[this.pos]; +}; + +EL.Parser.prototype.consumeChar = function() { + if (this.pos >= this.data.length) throw(this.error('eof')); + return this.data[this.pos++]; +}; + +EL.Parser.prototype.consumeWhitespace = function() { + var c; + while ((c = this.peek()) && c.match(/[\s\n]/)) { + this.consumeChar(); + } +}; + +EL.Parser.prototype.rest = function() { + return this.data.substring(this.pos, this.data.length); +}; + +EL.Parser.prototype.moreInput = function() { + return (this.pos < this.data.length); +}; + +EL.Parser.prototype.parse = function() { + this.pos = 0; + var exprs = []; + while (this.moreInput()) { + try { + exprs.push(this.parseExpression()); + } catch (e) { + if (e.parserError && e.parserError() && e.name == 'eof') { + print("error: " + e.message); + break; + } + else { + throw(e); + } + } + } + this.expressions = exprs; + this.prettyPrint(exprs); + return exprs; +}; + +EL.Parser.prototype.parseUntil = function(regex, initial, next) { + var c, + token = initial, + condition = function(c){ return c.match(regex) == null; }; + while ((c = this.peek()) && condition(c)) { + token = next(token, this.consumeChar()); + } + if (this.peek()) this.consumeChar(); // consume terminator + return token; +}; + +EL.Parser.prototype.parseSexp = function() { + var sexp = [], + token; + // consume initial paren '(' + this.consumeChar(); + while ((expr = this.parseExpression()) && expr != ')') { + sexp.push(expr); + } + return sexp; +}; + +EL.Parser.prototype.parseString = function() { + // consume initial quotation mark + this.consumeChar(); + var self = this; + return this.parseUntil(/"/, '', function(s,c){ + if (c == '\\') { + c = self.consumeChar(); + } + return s + c; + }); +}; + +EL.Parser.prototype.parseSymbol = function() { + var symbol = this.parseUntil(/[\s()]/, '', function(t,c){return t + c;}); + return symbol; +}; + +// Only integers +EL.Parser.prototype.parseNumber = function() { + return this.parseUntil(/[^\d]/, 0, function(n,c){return n*10 + parseInt(c);}); + var c, + num = 0; + while ((c = this.peek()) && c.match(/\d/)) { + num *= 10; + num += parseInt(this.consumeChar()); + } + return num; +}; + +EL.Parser.prototype.lookingAtNumber = function() { + var pos = this.pos, + rest = this.rest(), + match = rest.match(/^\d+\b/) || rest.match(/^\d+$/); + return (match != null); +}; + +EL.Parser.prototype.parseExpression = function() { + var value, + c = this.peek(); + if (c == '(') { + print("SEXP("); + value = ['sexp', this.parseSexp()]; + } + else if (c == ')') { + print(")"); + return this.consumeChar(); + } + else if (c == "'") { + print("QUOTE"); + this.consumeChar(); + var expr = this.parseExpression(), + quote = [['symbol', 'quote']]; + quote.push(expr); + value = ['sexp', quote]; + } + else if (c == '"') { + print("STRING"); + value = ['string', this.parseString()]; + } + else if (this.lookingAtNumber()) { + print("NUMBER"); + value = ['number', this.parseNumber()]; + } + else if (c) { + print("SYMBOL"); + value = ['symbol', this.parseSymbol()]; + } + this.consumeWhitespace(); + return value; +}; + +EL.Parser.prototype.prettyPrint = function(x, indent, key) { + var typeOf = function(value) { + var s = typeof value; + if (s === 'object') { + if (value) { + if (typeof value.length === 'number' && + !(value.propertyIsEnumerable('length')) && + typeof value.splice === 'function') { + s = 'array'; + } + } else { + s = 'null'; + } + } + return s; + }; + + if (indent === undefined) { + indent = 0; + } + + var space = ""; + for (var j = 0; j < indent; j++) { + space += " "; + } + + switch (typeOf(x)) { + case 'object': + if (key) { + print(space + key + ': {'); + } + else { + print(space + '{'); + } + for (var a in x) { + this.prettyPrint(x[a], 1+indent, a); + } + print(space + "},"); + break; + + case 'string': + if (key) { + print(space + key + ': "' + x + '",'); + } + else { + print(space + '"' + x + '",'); + } + break; + + case 'array': + if (key) { + print(space + key + ': ['); + } + else { + print(space + '['); + } + var n = x.length, i = 0; + while (i < n) { + this.prettyPrint(x[i++], 1+indent); + } + print(space + '],'); + break; + + case 'null': + if (key) { + print(space + key + ': (null),'); + } + else { + print(space + '(null),'); + } + break; + + default: + if (key) { + print(space + key + ": " + x + ','); + } + else { + print(space + x + ','); + } + break; + } +};