diff --git a/bin/uglifyjs b/bin/uglifyjs index 59df920a..b052eb26 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -25,7 +25,7 @@ var options = { output: true // stdout }; -var args = jsp.slice(process.argv, 2); +var args = process.argv.slice(2); var filename; out: while (args.length > 0) { diff --git a/lib/constants.js b/lib/constants.js new file mode 100644 index 00000000..c7ca5bfe --- /dev/null +++ b/lib/constants.js @@ -0,0 +1,369 @@ +/*********************************************************************** + + A JavaScript tokenizer / parser / beautifier / compressor. + + This file defines some constants and utility functions that are used + in the parser and code generator. + + -------------------------------- (C) --------------------------------- + + Author: Mihai Bazon + + http://mihai.bazon.net/blog + + Distributed under a ZLIB license: + + Copyright 2010 (c) Mihai Bazon + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any + damages arising from the use of this software. + + Permission is granted to anyone to use this software for any + purpose, including commercial applications, and to alter it and + redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must + not claim that you wrote the original software. If you use this + software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must + not be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. + + ***********************************************************************/ + +/* -----[ Utils ]----- */ + +function array_to_hash(a) { + var ret = {}; + for (var i = 0; i < a.length; ++i) + ret[a[i]] = true; + return ret; +}; +exports.array_to_hash = array_to_hash; + +function curry(f) { + var args = slice(arguments, 1); + return function() { return f.apply(this, args.concat(slice(arguments))); }; +}; +exports.curry = curry; + +function prog1(ret) { + if (ret instanceof Function) + ret = ret(); + for (var i = 1, n = arguments.length; --n > 0; ++i) + arguments[i](); + return ret; +}; +exports.prog1 = prog1; + +function slice(a, start) { + return Array.prototype.slice.call(a, start == null ? 0 : start); +}; +exports.slice = slice; + +function characters(str) { + return str.split(""); +}; +exports.characters = characters; + +function member(name, array) { + for (var i = array.length; --i >= 0;) + if (array[i] === name) + return true; + return false; +}; +exports.member = member; + +function HOP(obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); +}; +exports.HOP = HOP; + +function is_alphanumeric_char(ch) { + ch = ch.charCodeAt(0); + return (ch >= 48 && ch <= 57) || + (ch >= 65 && ch <= 90) || + (ch >= 97 && ch <= 122); +}; +exports.is_alphanumeric_char = is_alphanumeric_char; + +function is_identifier_char(ch) { + return is_alphanumeric_char(ch) || ch == "$" || ch == "_"; +}; +exports.is_identifier_char = is_identifier_char; + +function is_identifier(name) { + return /^[a-z_$][a-z0-9_$]*$/i.test(name) && + !HOP(KEYWORDS_ATOM, name) && + !HOP(RESERVED_WORDS, name) && + !HOP(KEYWORDS, name); +}; +exports.is_identifier = is_identifier; + +function is_digit(ch) { + ch = ch.charCodeAt(0); + return ch >= 48 && ch <= 57; +}; +exports.is_digit = is_digit; + +function repeat_string(str, i) { + if (i <= 0) return ""; + if (i == 1) return str; + var d = repeat_string(str, i >> 1); + d += d; + if (i & 1) d += str; + return d; +}; +exports.repeat_string = repeat_string; + +function defaults(args, defs) { + var ret = {}; + if (args === true) + args = {}; + for (var i in defs) if (HOP(defs, i)) { + ret[i] = (args && HOP(args, i)) ? args[i] : defs[i]; + } + return ret; +}; +exports.defaults = defaults; + +function make_deep_copy(src) { + if (src === null) + return null; + if (src instanceof Boolean || src === true || src === false) + return src; + if (src instanceof Number || typeof src == "number") + return src; + if (src instanceof String || typeof src == "string") + return src; + if (src.clone instanceof Function) + return src.clone(); + if (src instanceof Array) + return src.map(make_deep_copy); + if (src instanceof Function) + return src; + if (src instanceof Date) + return new Date(src); + if (src instanceof Object) { + var i, dest = {}; + for (i in src) if (HOP(src, i)) { + dest[i] = make_deep_copy(src[i]); + } + return dest; + } + throw new Error("Cannot clone object: " + src); +}; +exports.make_deep_copy = make_deep_copy; + +/* -----[ Contants ]----- */ + +var KEYWORDS = array_to_hash([ + "break", + "case", + "catch", + "const", + "continue", + "default", + "delete", + "do", + "else", + "finally", + "for", + "function", + "if", + "in", + "instanceof", + "new", + "return", + "switch", + "throw", + "try", + "typeof", + "var", + "void", + "while", + "with", + "NaN" +]); +exports.KEYWORDS = KEYWORDS; + +var RESERVED_WORDS = array_to_hash([ + "abstract", + "boolean", + "byte", + "char", + "class", + "debugger", + "double", + "enum", + "export", + "extends", + "final", + "float", + "goto", + "implements", + "import", + "int", + "interface", + "long", + "native", + "package", + "private", + "protected", + "public", + "short", + "static", + "super", + "synchronized", + "throws", + "transient", + "volatile" +]); +exports.RESERVED_WORDS = RESERVED_WORDS; + +var KEYWORDS_BEFORE_EXPRESSION = array_to_hash([ + "return", + "new", + "delete", + "throw", + "else" +]); +exports.KEYWORDS_BEFORE_EXPRESSION = KEYWORDS_BEFORE_EXPRESSION; + +var KEYWORDS_ATOM = array_to_hash([ + "false", + "null", + "true", + "undefined", + "NaN" +]); +exports.KEYWORDS_ATOM = KEYWORDS_ATOM; + +var OPERATOR_CHARS = array_to_hash(characters("+-*&%=<>!?|~^")); +exports.OPERATOR_CHARS = OPERATOR_CHARS; + +var OPERATORS = array_to_hash([ + "in", + "instanceof", + "typeof", + "new", + "void", + "delete", + "++", + "--", + "+", + "-", + "!", + "~", + "&", + "|", + "^", + "*", + "/", + "%", + ">>", + "<<", + ">>>", + "<", + ">", + "<=", + ">=", + "==", + "===", + "!=", + "!==", + "?", + "=", + "+=", + "-=", + "/=", + "*=", + "%=", + ">>=", + "<<=", + ">>>=", + "~=", + "%=", + "|=", + "^=", + "&&", + "||" +]); +exports.OPERATORS = OPERATORS; + +var WHITESPACE_CHARS = array_to_hash(characters(" \n\r\t")); +exports.WHITESPACE_CHARS = WHITESPACE_CHARS; + +var PUNC_BEFORE_EXPRESSION = array_to_hash(characters("[{}(,.;:")); +exports.PUNC_BEFORE_EXPRESSION = PUNC_BEFORE_EXPRESSION; + +var PUNC_CHARS = array_to_hash(characters("[]{}(),;:")); +exports.PUNC_CHARS = PUNC_CHARS; + +var REGEXP_MODIFIERS = array_to_hash(characters("gmsiy")); +exports.REGEXP_MODIFIERS = REGEXP_MODIFIERS; + +var UNARY_PREFIX = array_to_hash([ + "typeof", + "void", + "delete", + "--", + "++", + "!", + "~", + "-", + "+" +]); +exports.UNARY_PREFIX = UNARY_PREFIX; + +var UNARY_POSTFIX = array_to_hash([ "--", "++" ]); +exports.UNARY_POSTFIX = UNARY_POSTFIX; + +var ASSIGNMENT = (function(a, ret, i){ + while (i < a.length) { + ret[a[i]] = a[i].substr(0, a[i].length - 1); + i++; + } + return ret; +})( + ["+=", "-=", "/=", "*=", "%=", ">>=", "<<=", ">>>=", "~=", "%=", "|=", "^="], + { "=": true }, + 0 +); +exports.ASSIGNMENT = ASSIGNMENT; + +var PRECEDENCE = (function(a, ret){ + for (var i = 0, n = 1; i < a.length; ++i, ++n) { + var b = a[i]; + for (var j = 0; j < b.length; ++j) { + ret[b[j]] = n; + } + } + return ret; +})( + [ + ["||"], + ["&&"], + ["|"], + ["^"], + ["&"], + ["==", "===", "!=", "!=="], + ["<", ">", "<=", ">=", "in", "instanceof"], + [">>", "<<", ">>>"], + ["+", "-"], + ["*", "/", "%"] + ], + {} +); +exports.PRECEDENCE = PRECEDENCE; + +var STATEMENTS_WITH_LABELS = array_to_hash([ "for", "do", "while", "switch" ]); +exports.STATEMENTS_WITH_LABELS = STATEMENTS_WITH_LABELS; + +var ATOMIC_START_TOKEN = array_to_hash([ "atom", "num", "string", "regexp", "name" ]); +exports.ATOMIC_START_TOKEN = ATOMIC_START_TOKEN; diff --git a/lib/macro-js.js b/lib/macro-js.js new file mode 100644 index 00000000..ae80797c --- /dev/null +++ b/lib/macro-js.js @@ -0,0 +1,395 @@ +var ParseJS = require("./parse-js").ParseJS; +var pro = require("./process"); +var $C = require("./constants"); +var HOP = $C.HOP; + +var prog1 = $C.prog1; +var curry = $C.curry; + +function Unquote(ast, splice) { this.ast = ast; this.splice = splice; }; + +function Symbol(sym) { this.sym = sym; }; + +Symbol.prototype.toString = function() { return this.sym; }; + +function quote_ast(ast) { + if (ast === null) { + return [ "name", "null" ]; + } + else if (typeof ast == "undefined") { + return [ "name", "undefined" ]; + } + else if (ast instanceof Unquote) { + if (ast.splice) { + return [ "array", [ [ "string", "splice" ], ast.ast ] ]; + } else { + return ast.ast; + } + } + else if (ast instanceof Symbol) { + return ast; + } + else if (ast instanceof Array) { + return [ "array", ast.map(quote_ast) ]; + } + else if (typeof ast == "string") { + return [ "string", ast ]; + } + else if (typeof ast == "boolean") { + return [ "name", ast.toString() ]; + } + else if (typeof ast == "number") { + return isNaN(ast) + ? [ "name", "NaN" ] + : [ "num", ast ]; + } + else throw new Error("Unhandled case in quote: " + typeof ast + "\n" + sys.inspect(ast, null, null)); +}; + +function createParser() { + var parser = new ParseJS(); + var SYM = 0; + parser.gensym = function() { + return new Symbol("__$$__SYM" + (++SYM)); + }; + parser.symbol = function(name) { + return new Symbol(name); + }; + parser.macro_expand = function(ast) { + return macro_expand(parser, ast); + }; + parser.splice_list = function(list) { + return [ "splice", list ]; + }; + parser.quote = quote_ast; + parser.macros = {}; + parser.define_token_reader("`", function(TC, OC) { + TC.next(); + var tok = TC.next_token(); + tok.macro = "quote"; + return tok; + }); + parser.define_token_reader("@", function(TC, OC) { + TC.next(); + var tok = TC.next_token(); + tok.macro = "quote-stmt"; + return tok; + }); + parser.define_token_reader("\\", function(TC, OC) { + TC.next(); + var tok = TC.next_token(); + if (tok.macro == "quote-stmt") tok.macro = "splice"; + else tok.macro = "unquote"; + return tok; + }); + parser.define_statement("defmacro", function(PC, OC) { + var m = read_defmacro(PC, OC); + return compile_macro(m.name, m.args, m.body, parser); + }); + parser.define_statement("defstat", function(PC, OC) { + // what happens here is really quite tricky: if + // immediately after "defstat" you use the new + // statement, it won't be seen as a keyword because + // the token has already been peek()-ed. Hence, we + // use a hack -- passing true to readDefMacro will + // register the new name as a keyword immediately. + var m = read_defmacro(PC, OC, true); + parser.define_statement(m.name, function(PC, OC) { + var a = []; + for (var i = 0; i < m.args.length; ++i) + a[i] = m.args[i].reader(); + return [ "macro-expand", m.name, a ]; + }); + return compile_macro(m.name, m.args, m.body, parser, true); + }); + parser.define_token_processor(function(cont, PC, OC){ + var tok = PC.token(); + if (!tok.macro) + return cont(); + switch (tok.macro) { + case "quote": + return quote_ast(cont()); + case "quote-stmt": + return quote_ast(PC.statement()); + case "unquote": + return new Unquote(cont()); + case "splice": + return new Unquote(cont(), true); + default: + throw new Error("Unsupported macro character: " + tok.macro); + } + }); + var orig_parse = parser.parse; + parser.parse = function() { + return macro_expand(parser, orig_parse.apply(this, arguments)); + }; + return parser; +}; + +function read_defmacro_args(PC, OC) { + var args = []; + PC.expect("("); + get_list(0); + function get_list(level) { + var first = true; + var optional = false; + while (!PC.is("punc", ")")) { + if (!first) PC.expect(","); + if (PC.is("punc", "(")) { + PC.next(); + get_list(level + 1); + } else { + if (!PC.is("name")) PC.unexpected(); + var a = { + name : PC.tokval(), + optional : optional + }; + a.level = level; + PC.next(); + if (PC.is("punc", ":")) { + PC.next(); + if (!(PC.is("name") || PC.is("keyword"))) + PC.unexpected(); + switch (a.type = PC.tokval()) { + case "block": + a.reader = function() { + return [ "block", [ PC.tokprocess(PC.block_) ] ]; + }; + break; + case "statement": + a.reader = function() { + return [ "block", [ PC.tokprocess(PC.statement) ] ]; + }; + break; + case "name": + a.reader = function() { + return new Symbol(prog1(curry(PC.tokprocess, function(){ + if (!PC.is("name")) PC.unexpected(); + return PC.tokval(); + }), PC.next)); + }; + break; + default: + PC.unexpected(); + } + PC.next(); + } else { + a.reader = curry(PC.expression, false); + } + if (PC.is("operator", "*")) { + if (level == 0) + throw new Error("'*' can appear only in nested argument list"); + a.rest = true; + a.reader = (function(orig){ + return function() { + var a = [], first = true; + PC.expect("("); + while (!PC.is("punc", ")")) { + if (first) first = false; else PC.expect(","); + a.push(orig()); + } + PC.next(); + return a; + }; + })(a.reader); + PC.next(); + } + else if (PC.is("operator", "?")) { + optional = a.optional = true; + a.reader = (function(orig){ + return function() { + }; + })(a.reader); + PC.next(); + } + args.push(a); + } + first = false; + } + PC.next(); // skip closing paren + }; + return args; +}; + +function read_defmacro(PC, OC, make_kw) { + //*** read macro name + if (!PC.is("name")) PC.unexpected(); + var name = PC.tokval(); + // this is needed for defstat. + if (make_kw) + OC.self.define_keyword(name); + PC.next(); + //*** read arguments list + var args = read_defmacro_args(PC, OC); + //*** read body; set in_function so that "return" is allowed. + PC.S.in_function++; + var body = PC.block_(); + PC.S.in_function--; + return { name: name, args: args, body: body }; +}; + +function compile_macro(name, args, body, parser, statement_only) { + if (HOP(parser.macros, name)) { + throw new Error("Redefinition of macro '" + name + "'"); + } + var ast = [ + "defun", + name, + args.map(function(a) { return a.name }), + body + ]; + var code = pro.gen_code(ast, { plainsyms: true }); + var func; + try { func = new Function("return " + code).call(parser); } catch(ex) { + sys.puts("Error compiling macro '" + name + "'"); + sys.puts(code); + sys.puts(ex.toString()); + throw ex; + } + parser.macros[name] = { + args: args, + func: func + }; + if (!statement_only) { + parser.define_call_parser(name, function(PC, OC){ + PC.expect("("); + var first = true, a = []; + while (!PC.is("punc", ")")) { + if (first) first = false; else PC.expect(","); + a.push(args[a.length].reader()); + } + PC.next(); + return [ "macro-expand", name, a ]; + }); + } + return [ "comment2", "*** // Macro '" + name + "' compiled as:\n" + code + " ***" ]; +}; + +function macro_expand(parser, ast) { + var w = pro.ast_walker(); + return normalize_ast(w.with_walkers({ + "macro-expand": function(macro, args) { + var func = parser.macros[macro].func; + args = args.map(w.walk); + var ret = func.apply(parser, args); + ret = replace_symbols(ret); + ret = w.walk(ret); + return ret; + }, + "-other": function() { + return this; + // if (this[0] instanceof Symbol) + // return this; + } + }, function() { + return w.walk(ast); + })); +}; + +function normalize_symbol(s, wantname) { + if (s instanceof Symbol) { + return wantname + ? [ "name", s.toString() ] + : s.toString(); + } + else if (s instanceof Array && s[0] == "name" && !wantname) { + return s[1]; + } + return s; +}; + +function replace_symbols(ast) { + if (ast instanceof Array) { + switch (ast[0]) { + case "var": + case "const": + case "object": + ast[1].forEach(function(def){ + def[0] = normalize_symbol(def[0]); + if (def[1]) + def[1] = replace_symbols(def[1]); + }); + return ast; + case "function": + case "defun": + ast[1] = normalize_symbol(ast[1]); + if (ast[2][0] instanceof Array) ast[2] = ast[2][0]; + if (ast[2][0] == "splice") ast[2] = ast[2][1]; + ast[2] = ast[2].map(function(name){ return normalize_symbol(name) }); + ast[3] = ast[3].map(replace_symbols); + return ast; + case "try": + // 0 block, 1 catch: 1.0 ex, 1.1 block, 2 finally + ast[0] = replace_symbols(ast[0]); + if (ast[1]) { + ast[1][0] = normalize_symbol(ast[1][0]); + ast[1][1] = ast[1][1].map(replace_symbols); + } + if (ast[2]) { + ast[2] = ast[2].map(replace_symbols); + } + return ast; + default: + for (var i = 0; i < ast.length; ++i) + ast[i] = replace_symbols(ast[i]); + return ast; + } + } else { + return normalize_symbol(ast, true); + } + return ast; +}; + +function normalize_ast(ast) { + ast = replace_symbols(ast); + var w = pro.ast_walker(); + return w.with_walkers({ + "stat": function(expr) { + expr = w.walk(expr); + switch (expr[0]) { + case "block": + if (expr[1] && expr[1].length == 1) + return expr[1][0]; + return expr; + case "break": + case "const": + case "continue": + case "defun": + case "do": + case "for": + case "for-in": + case "if": + case "return": + case "switch": + case "throw": + case "try": + case "var": + case "while": + case "with": + return expr; + } + }, + "splice": function(a) { + if (w.parent()[0] == "stat" && a[0] == "block") { + if (a[1].length > 0 && a[1][0] instanceof Array) { + a[1] = a[1][0]; + } + return a[0]; + } else { + for (var i = 0; i < a.length; ++i) + this[i] = a[i]; + this.length = a.length; + return this; + } + }, + "-other": function() { + return this; + } + }, function() { + return w.walk(ast); + }); +}; + +/* -----[ Exports ]----- */ + +exports.createParser = createParser; diff --git a/lib/parse-js.js b/lib/parse-js.js index 4dd8459b..f9117fc1 100644 --- a/lib/parse-js.js +++ b/lib/parse-js.js @@ -50,194 +50,35 @@ ***********************************************************************/ -/* -----[ Tokenizer (constants) ]----- */ - -var KEYWORDS = array_to_hash([ - "break", - "case", - "catch", - "const", - "continue", - "default", - "delete", - "do", - "else", - "finally", - "for", - "function", - "if", - "in", - "instanceof", - "new", - "return", - "switch", - "throw", - "try", - "typeof", - "var", - "void", - "while", - "with", - "NaN" -]); - -var RESERVED_WORDS = array_to_hash([ - "abstract", - "boolean", - "byte", - "char", - "class", - "debugger", - "double", - "enum", - "export", - "extends", - "final", - "float", - "goto", - "implements", - "import", - "int", - "interface", - "long", - "native", - "package", - "private", - "protected", - "public", - "short", - "static", - "super", - "synchronized", - "throws", - "transient", - "volatile" -]); - -var KEYWORDS_BEFORE_EXPRESSION = array_to_hash([ - "return", - "new", - "delete", - "throw", - "else" -]); - -var KEYWORDS_ATOM = array_to_hash([ - "false", - "null", - "true", - "undefined", - "NaN" -]); - -var OPERATOR_CHARS = array_to_hash(characters("+-*&%=<>!?|~^")); - -var RE_HEX_NUMBER = /^0x[0-9a-f]+$/i; -var RE_OCT_NUMBER = /^0[0-7]+$/; -var RE_DEC_NUMBER = /^\d*\.?\d*(?:e-?\d*(?:\d\.?|\.?\d)\d*)?$/i; - -var OPERATORS = array_to_hash([ - "in", - "instanceof", - "typeof", - "new", - "void", - "delete", - "++", - "--", - "+", - "-", - "!", - "~", - "&", - "|", - "^", - "*", - "/", - "%", - ">>", - "<<", - ">>>", - "<", - ">", - "<=", - ">=", - "==", - "===", - "!=", - "!==", - "?", - "=", - "+=", - "-=", - "/=", - "*=", - "%=", - ">>=", - "<<=", - ">>>=", - "~=", - "%=", - "|=", - "^=", - "&&", - "||" -]); - -var WHITESPACE_CHARS = array_to_hash(characters(" \n\r\t")); - -var PUNC_BEFORE_EXPRESSION = array_to_hash(characters("[{}(,.;:")); - -var PUNC_CHARS = array_to_hash(characters("[]{}(),;:")); - -var REGEXP_MODIFIERS = array_to_hash(characters("gmsiy")); - -/* -----[ Tokenizer ]----- */ - -function is_alphanumeric_char(ch) { - ch = ch.charCodeAt(0); - return (ch >= 48 && ch <= 57) || - (ch >= 65 && ch <= 90) || - (ch >= 97 && ch <= 122); -}; - -function is_identifier_char(ch) { - return is_alphanumeric_char(ch) || ch == "$" || ch == "_"; -}; - -function is_digit(ch) { - ch = ch.charCodeAt(0); - return ch >= 48 && ch <= 57; -}; - -function parse_js_number(num) { - if (RE_HEX_NUMBER.test(num)) { - return parseInt(num.substr(2), 16); - } else if (RE_OCT_NUMBER.test(num)) { - return parseInt(num.substr(1), 8); - } else if (RE_DEC_NUMBER.test(num)) { - return parseFloat(num); - } -}; - -function JS_Parse_Error(message, line, col, pos) { - this.message = message; - this.line = line; - this.col = col; - this.pos = pos; - try { - ({})(); - } catch(ex) { - this.stack = ex.stack; +var $C = require("./constants"); + +var make_deep_copy = $C.make_deep_copy; + +var parse_js_number = (function(){ + var RE_HEX_NUMBER = /^0x[0-9a-f]+$/i; + var RE_OCT_NUMBER = /^0[0-7]+$/; + var RE_DEC_NUMBER = /^\d*\.?\d*(?:e-?\d*(?:\d\.?|\.?\d)\d*)?$/i; + return function(num) { + if (RE_HEX_NUMBER.test(num)) { + return parseInt(num.substr(2), 16); + } else if (RE_OCT_NUMBER.test(num)) { + return parseInt(num.substr(1), 8); + } else if (RE_DEC_NUMBER.test(num)) { + return parseFloat(num); + } }; -}; - -JS_Parse_Error.prototype.toString = function() { - return this.message + " (line: " + this.line + ", col: " + this.col + ", pos: " + this.pos + ")" + "\n\n" + this.stack; -}; +})(); function js_error(message, line, col, pos) { - throw new JS_Parse_Error(message, line, col, pos); + var err = new Error(message); + err.type = "ParseJS"; + err.line = line; + err.col = col; + err.pos = pos; + err.toString = function() { + return message + "\nline: " + line + "\ncol: " + col + "\npos: " + pos; + }; + throw err; }; function is_token(token, type, val) { @@ -246,6 +87,44 @@ function is_token(token, type, val) { var EX_EOF = {}; +function ParseJS(){ + +// import stuff we need. + +var ASSIGNMENT = make_deep_copy($C.ASSIGNMENT); +var ATOMIC_START_TOKEN = make_deep_copy($C.ATOMIC_START_TOKEN); +var HOP = make_deep_copy($C.HOP); +var KEYWORDS = make_deep_copy($C.KEYWORDS); +var KEYWORDS_ATOM = make_deep_copy($C.KEYWORDS_ATOM); +var KEYWORDS_BEFORE_EXPRESSION = make_deep_copy($C.KEYWORDS_BEFORE_EXPRESSION); +var OPERATORS = make_deep_copy($C.OPERATORS); +var OPERATOR_CHARS = make_deep_copy($C.OPERATOR_CHARS); +var PRECEDENCE = make_deep_copy($C.PRECEDENCE); +var PUNC_BEFORE_EXPRESSION = make_deep_copy($C.PUNC_BEFORE_EXPRESSION); +var PUNC_CHARS = make_deep_copy($C.PUNC_CHARS); +var REGEXP_MODIFIERS = make_deep_copy($C.REGEXP_MODIFIERS); +var STATEMENTS_WITH_LABELS = make_deep_copy($C.STATEMENTS_WITH_LABELS); +var UNARY_POSTFIX = make_deep_copy($C.UNARY_POSTFIX); +var UNARY_PREFIX = make_deep_copy($C.UNARY_PREFIX); +var WHITESPACE_CHARS = make_deep_copy($C.WHITESPACE_CHARS); +var RESERVED_WORDS = make_deep_copy($C.RESERVED_WORDS); +var curry = make_deep_copy($C.curry); +var is_alphanumeric_char = make_deep_copy($C.is_alphanumeric_char); +var is_digit = make_deep_copy($C.is_digit); +var is_identifier_char = make_deep_copy($C.is_identifier_char); +var member = make_deep_copy($C.member); +var prog1 = make_deep_copy($C.prog1); +var slice = make_deep_copy($C.slice); + +// isn't CommonJS great? + +var CUSTOM_STATEMENT_PARSERS = {}; +var CUSTOM_CALL_PARSERS = {}; +var CUSTOM_TOKEN_READER = {}; +var CUSTOM_TOKEN_PROCESSOR = null; + +/* -----[ Tokenizer ]----- */ + function tokenizer($TEXT, skip_comments) { var S = { @@ -276,10 +155,6 @@ function tokenizer($TEXT, skip_comments) { return ch; }; - function eof() { - return !S.peek(); - }; - function find(what, signal_eof) { var pos = S.text.indexOf(what, S.pos); if (signal_eof && pos == -1) throw EX_EOF; @@ -501,10 +376,14 @@ function tokenizer($TEXT, skip_comments) { } }; - function next_token() { + function next_token(force_regexp) { + if (force_regexp) + return read_regexp(); skip_whitespace(); - start_token(); var ch = peek(); + if (CUSTOM_TOKEN_READER[ch]) + return CUSTOM_TOKEN_READER[ch](tokenizer_context, obj_context); + start_token(); if (!ch) return token("eof"); if (is_digit(ch)) return read_num(); if (ch == '"' || ch == "'") return read_string(); @@ -516,6 +395,32 @@ function tokenizer($TEXT, skip_comments) { parse_error("Unexpected character '" + ch + "'"); }; + var tokenizer_context = { + S : S, + peek : peek, + next : next, + find : find, + start_token : start_token, + token : token, + skip_whitespace : skip_whitespace, + read_while : read_while, + parse_error : parse_error, + read_num : read_num, + read_escaped_char : read_escaped_char, + hex_bytes : hex_bytes, + read_string : read_string, + read_line_comment : read_line_comment, + read_multiline_comment : read_multiline_comment, + read_regexp : read_regexp, + read_operator : read_operator, + handle_slash : handle_slash, + handle_dot : handle_dot, + read_word : read_word, + with_eof_error : with_eof_error, + next_token : next_token, + is_token : is_token + }; + next_token.context = function(nc) { if (nc) S = nc; return S; @@ -525,73 +430,9 @@ function tokenizer($TEXT, skip_comments) { }; -/* -----[ Parser (constants) ]----- */ - -var UNARY_PREFIX = array_to_hash([ - "typeof", - "void", - "delete", - "--", - "++", - "!", - "~", - "-", - "+" -]); - -var UNARY_POSTFIX = array_to_hash([ "--", "++" ]); - -var ASSIGNMENT = (function(a, ret, i){ - while (i < a.length) { - ret[a[i]] = a[i].substr(0, a[i].length - 1); - i++; - } - return ret; -})( - ["+=", "-=", "/=", "*=", "%=", ">>=", "<<=", ">>>=", "~=", "%=", "|=", "^="], - { "=": true }, - 0 -); - -var PRECEDENCE = (function(a, ret){ - for (var i = 0, n = 1; i < a.length; ++i, ++n) { - var b = a[i]; - for (var j = 0; j < b.length; ++j) { - ret[b[j]] = n; - } - } - return ret; -})( - [ - ["||"], - ["&&"], - ["|"], - ["^"], - ["&"], - ["==", "===", "!=", "!=="], - ["<", ">", "<=", ">=", "in", "instanceof"], - [">>", "<<", ">>>"], - ["+", "-"], - ["*", "/", "%"] - ], - {} -); - -var STATEMENTS_WITH_LABELS = array_to_hash([ "for", "do", "while", "switch" ]); - -var ATOMIC_START_TOKEN = array_to_hash([ "atom", "num", "string", "regexp", "name" ]); - /* -----[ Parser ]----- */ -function NodeWithToken(str, start, end) { - this.name = str; - this.start = start; - this.end = end; -}; - -NodeWithToken.prototype.toString = function() { return this.name; }; - -function parse($TEXT, strict_mode, embed_tokens) { +function parse($TEXT, strict_mode) { var S = { input: tokenizer($TEXT, true), @@ -605,6 +446,12 @@ function parse($TEXT, strict_mode, embed_tokens) { S.token = next(); + function tokprocess(cont) { + return CUSTOM_TOKEN_PROCESSOR + ? CUSTOM_TOKEN_PROCESSOR(cont, parser_context, obj_context) + : cont(); + }; + function is(type, value) { return is_token(S.token, type, value); }; @@ -641,14 +488,14 @@ function parse($TEXT, strict_mode, embed_tokens) { function unexpected(token) { if (token == null) token = S.token; - token_error(token, "Unexpected token: " + token.type + " (" + token.value + ")"); + token_error(token, "Unexpected token: " + token.type + " \"" + token.value + "\""); }; function expect_token(type, val) { if (is(type, val)) { return next(); } - token_error(S.token, "Unexpected token " + S.token.type + ", expected " + type); + token_error(S.token, "Unexpected token " + S.token.type + " \"" + S.token.value + "\", expected " + type + " \"" + val + "\""); }; function expect(punc) { return expect_token("punc", punc); }; @@ -665,7 +512,9 @@ function parse($TEXT, strict_mode, embed_tokens) { }; function as() { - return slice(arguments); + return make_node + ? make_node(parser_context, obj_context, slice(arguments)) + : slice(arguments); }; function parenthesised() { @@ -675,18 +524,11 @@ function parse($TEXT, strict_mode, embed_tokens) { return ex; }; - function add_tokens(str, start, end) { - return new NodeWithToken(str, start, end); - }; - - var statement = embed_tokens ? function() { - var start = S.token; - var stmt = $statement(); - stmt[0] = add_tokens(stmt[0], start, prev()); - return stmt; - } : $statement; - - function $statement() { + function statement() { + if (is("operator", "/")) { + S.peeked = null; + S.token = S.input(true); // force regexp + } switch (S.token.type) { case "num": case "string": @@ -710,12 +552,14 @@ function parse($TEXT, strict_mode, embed_tokens) { case ";": next(); return as("block"); - default: - unexpected(); } + unexpected(); case "keyword": - switch (prog1(S.token.value, next)) { + var kw = prog1(S.token.value, next); + if (CUSTOM_STATEMENT_PARSERS[kw]) + return CUSTOM_STATEMENT_PARSERS[kw](parser_context, obj_context); + switch (kw) { case "break": return break_cont("break"); @@ -829,7 +673,7 @@ function parse($TEXT, strict_mode, embed_tokens) { }; function function_(in_statement) { - var name = is("name") ? prog1(S.token.value, next) : null; + var name = is("name") ? tokprocess(curry(prog1, S.token.value, next)) : null; if (in_statement && !name) unexpected(); expect("("); @@ -840,7 +684,7 @@ function parse($TEXT, strict_mode, embed_tokens) { while (!is("punc", ")")) { if (first) first = false; else expect(","); if (!is("name")) unexpected(); - a.push(S.token.value); + a.push(tokprocess(function() { return S.token.value })); next(); } next(); @@ -856,7 +700,7 @@ function parse($TEXT, strict_mode, embed_tokens) { }; function if_() { - var cond = parenthesised(), body = statement(), belse; + var cond = parenthesised(), body = statement(), belse = null; if (is("keyword", "else")) { next(); belse = statement(); @@ -906,9 +750,11 @@ function parse($TEXT, strict_mode, embed_tokens) { if (is("keyword", "catch")) { next(); expect("("); - if (!is("name")) - croak("Name expected"); - var name = S.token.value; + var name = tokprocess(function(){ + if (!is("name")) + croak("Name expected"); + return S.token.value; + }); next(); expect(")"); bcatch = [ name, block_() ]; @@ -927,7 +773,11 @@ function parse($TEXT, strict_mode, embed_tokens) { for (;;) { if (!is("name")) unexpected(); - var name = S.token.value; + var name = tokprocess(function(){ + if (!is("name")) + unexpected(); + return S.token.value; + }); next(); if (is("operator", "=")) { next(); @@ -971,7 +821,7 @@ function parse($TEXT, strict_mode, embed_tokens) { prog1(S.token.value, next), expr_atom(allow_calls)); } - if (is("punc")) { + if (is("punc")) return tokprocess(function(){ switch (S.token.value) { case "(": next(); @@ -984,17 +834,22 @@ function parse($TEXT, strict_mode, embed_tokens) { return subscripts(object_(), allow_calls); } unexpected(); - } - if (is("keyword", "function")) { + }); + if (is("keyword", "function")) return tokprocess(function(){ next(); return subscripts(function_(false), allow_calls); - } + }); if (HOP(ATOMIC_START_TOKEN, S.token.type)) { - var atom = S.token.type == "regexp" - ? as("regexp", S.token.value[0], S.token.value[1]) - : as(S.token.type, S.token.value); + var atom = tokprocess(function(){ + return S.token.type == "regexp" + ? as("regexp", S.token.value[0], S.token.value[1]) + : as(S.token.type, S.token.value); + }); return subscripts(prog1(atom, next), allow_calls); } + if (CUSTOM_TOKEN_PROCESSOR) { + return tokprocess(statement); // try to read as statement + } unexpected(); }; @@ -1034,21 +889,23 @@ function parse($TEXT, strict_mode, embed_tokens) { switch (S.token.type) { case "num": case "string": - return prog1(S.token.value, next); + return tokprocess(curry(prog1, S.token.value, next)); } return as_name(); }; function as_name() { - switch (S.token.type) { - case "name": - case "operator": - case "keyword": - case "atom": - return prog1(S.token.value, next); - default: - unexpected(); - } + return tokprocess(function(){ + switch (S.token.type) { + case "name": + case "operator": + case "keyword": + case "atom": + return prog1(S.token.value, next); + default: + unexpected(); + } + }); }; function subscripts(expr, allow_calls) { @@ -1061,6 +918,9 @@ function parse($TEXT, strict_mode, embed_tokens) { return subscripts(as("sub", expr, prog1(expression, curry(expect, "]"))), allow_calls); } if (allow_calls && is("punc", "(")) { + if (expr[0] == "name" && CUSTOM_CALL_PARSERS[expr[1]]) { + return CUSTOM_CALL_PARSERS[expr[1]](parser_context, obj_context); + } next(); return subscripts(as("call", expr, expr_list(")")), true); } @@ -1092,32 +952,30 @@ function parse($TEXT, strict_mode, embed_tokens) { return expr_op(expr_atom(true), 0); }; - function maybe_conditional(commas) { - if (arguments.length == 0) - commas = true; + function maybe_conditional() { var expr = expr_ops(); if (is("operator", "?")) { next(); - var yes = expression(); + var yes = expression(false); expect(":"); - return as("conditional", expr, yes, expression(commas)); + return as("conditional", expr, yes, expression(false)); } return expr; }; function is_assignable(expr) { - expr = expr[0]; - return expr == "name" || expr == "dot" || expr == "sub"; + // XXX: to support macro it's necessary not to check this + return true; + // expr = expr[0]; + // return expr == "name" || expr == "dot" || expr == "sub"; }; - function maybe_assign(commas) { - if (arguments.length == 0) - commas = true; - var left = maybe_conditional(commas), val = S.token.value; + function maybe_assign() { + var left = maybe_conditional(), val = S.token.value; if (is("operator") && HOP(ASSIGNMENT, val)) { if (is_assignable(left)) { next(); - return as("assign", ASSIGNMENT[val], left, maybe_assign(commas)); + return as("assign", ASSIGNMENT[val], left, maybe_assign()); } croak("Invalid assignment"); } @@ -1127,7 +985,7 @@ function parse($TEXT, strict_mode, embed_tokens) { function expression(commas) { if (arguments.length == 0) commas = true; - var expr = maybe_assign(commas); + var expr = maybe_assign(); if (commas && is("punc", ",")) { next(); return as("seq", expr, expression()); @@ -1136,12 +994,58 @@ function parse($TEXT, strict_mode, embed_tokens) { }; function in_loop(cont) { - try { - ++S.in_loop; - return cont(); - } finally { - --S.in_loop; - } + ++S.in_loop; + var ret = cont(); + --S.in_loop; + return ret; + }; + + var parser_context = { + S : S, + is : is, + peek : peek, + next : next, + prev : prev, + croak : croak, + token_error : token_error, + unexpected : unexpected, + expect_token : expect_token, + expect : expect, + semicolon : semicolon, + as : as, + parenthesised : parenthesised, + statement : statement, + labeled_statement : labeled_statement, + simple_statement : simple_statement, + break_cont : break_cont, + for_ : for_, + function_ : function_, + if_ : if_, + block_ : block_, + switch_block_ : switch_block_, + try_ : try_, + vardefs : vardefs, + var_ : var_, + const_ : const_, + new_ : new_, + expr_atom : expr_atom, + expr_list : expr_list, + array_ : array_, + object_ : object_, + as_property_name : as_property_name, + as_name : as_name, + subscripts : subscripts, + make_unary : make_unary, + expr_op : expr_op, + expr_ops : expr_ops, + maybe_conditional : maybe_conditional, + is_assignable : is_assignable, + maybe_assign : maybe_assign, + expression : expression, + in_loop : in_loop, + tokprocess : tokprocess, + token : function() { return S.token }, + tokval : function() { return S.token.value } }; return as("toplevel", (function(a){ @@ -1152,58 +1056,77 @@ function parse($TEXT, strict_mode, embed_tokens) { }; -/* -----[ Utilities ]----- */ + // API -function curry(f) { - var args = slice(arguments, 1); - return function() { return f.apply(this, args.concat(slice(arguments))); }; -}; + var obj_context = { + ASSIGNMENT : ASSIGNMENT, + ATOMIC_START_TOKEN : ATOMIC_START_TOKEN, + HOP : HOP, + KEYWORDS : KEYWORDS, + KEYWORDS_ATOM : KEYWORDS_ATOM, + KEYWORDS_BEFORE_EXPRESSION : KEYWORDS_BEFORE_EXPRESSION, + OPERATORS : OPERATORS, + OPERATOR_CHARS : OPERATOR_CHARS, + PRECEDENCE : PRECEDENCE, + PUNC_BEFORE_EXPRESSION : PUNC_BEFORE_EXPRESSION, + PUNC_CHARS : PUNC_CHARS, + REGEXP_MODIFIERS : REGEXP_MODIFIERS, + STATEMENTS_WITH_LABELS : STATEMENTS_WITH_LABELS, + UNARY_POSTFIX : UNARY_POSTFIX, + UNARY_PREFIX : UNARY_PREFIX, + WHITESPACE_CHARS : WHITESPACE_CHARS, + RESERVED_WORDS : RESERVED_WORDS, + curry : curry, + is_alphanumeric_char : is_alphanumeric_char, + is_digit : is_digit, + member : member, + prog1 : prog1, + slice : slice, -function prog1(ret) { - if (ret instanceof Function) - ret = ret(); - for (var i = 1, n = arguments.length; --n > 0; ++i) - arguments[i](); - return ret; -}; + self: this, + tokenizer: tokenizer, + parse: parse + }; -function array_to_hash(a) { - var ret = {}; - for (var i = 0; i < a.length; ++i) - ret[a[i]] = true; - return ret; -}; + var make_node = null; -function slice(a, start) { - return Array.prototype.slice.call(a, start == null ? 0 : start); -}; + this.tokenizer = tokenizer; + this.parse = parse; -function characters(str) { - return str.split(""); -}; + this.define_keyword = function(kw) { + KEYWORDS[kw] = kw; + RESERVED_WORDS[kw] = kw; + }; -function member(name, array) { - for (var i = array.length; --i >= 0;) - if (array[i] === name) - return true; - return false; -}; + this.define_call_parser = function(name, parser) { + CUSTOM_CALL_PARSERS[name] = parser; + }; + + this.define_statement = function(kw, parser) { + this.define_keyword(kw); + CUSTOM_STATEMENT_PARSERS[kw] = parser; + }; + + this.define_mknode = function(func) { + make_node = func; + }; + + this.define_token_reader = function(ch, reader) { + CUSTOM_TOKEN_READER[ch] = reader; + }; + + this.define_token_processor = function(func) { + CUSTOM_TOKEN_PROCESSOR = func; + }; -function HOP(obj, prop) { - return Object.prototype.hasOwnProperty.call(obj, prop); }; /* -----[ Exports ]----- */ -exports.tokenizer = tokenizer; -exports.parse = parse; -exports.slice = slice; -exports.curry = curry; -exports.member = member; -exports.array_to_hash = array_to_hash; -exports.PRECEDENCE = PRECEDENCE; -exports.KEYWORDS_ATOM = KEYWORDS_ATOM; -exports.RESERVED_WORDS = RESERVED_WORDS; -exports.KEYWORDS = KEYWORDS; -exports.ATOMIC_START_TOKEN = ATOMIC_START_TOKEN; -exports.is_alphanumeric_char = is_alphanumeric_char; +exports.ParseJS = ParseJS; + +// support old API +(function(P){ + exports.tokenizer = P.tokenizer; + exports.parse = P.parse; +})(new ParseJS()); diff --git a/lib/process.js b/lib/process.js index b072ad32..0164e081 100644 --- a/lib/process.js +++ b/lib/process.js @@ -52,10 +52,19 @@ ***********************************************************************/ -var jsp = require("./parse-js"), - slice = jsp.slice, - member = jsp.member, - PRECEDENCE = jsp.PRECEDENCE; +var $C = require("./constants"); + +// "import" things we need. + +var HOP = $C.HOP; +var PRECEDENCE = $C.PRECEDENCE; +var array_to_hash = $C.array_to_hash; +var defaults = $C.defaults; +var is_alphanumeric_char = $C.is_alphanumeric_char; +var is_identifier = $C.is_identifier; +var member = $C.member; +var repeat_string = $C.repeat_string; +var slice = $C.slice; /* -----[ helper for AST traversal ]----- */ @@ -192,7 +201,9 @@ function ast_walker(ast) { }, "atom": function(name) { return [ "atom", name ]; - } + }, + "comment1": function(name) { return this }, + "comment2": function(name) { return this } }; var user = {}; @@ -200,20 +211,29 @@ function ast_walker(ast) { function walk(ast) { if (ast == null) return null; - try { - stack.push(ast); - var type = ast[0]; - var gen = user[type]; - if (gen) { - var ret = gen.apply(ast, ast.slice(1)); - if (ret != null) - return ret; - } - gen = walkers[type]; - return gen.apply(ast, ast.slice(1)); - } finally { - stack.pop(); + // XXX: this helps us deal with generated names in macros. + { + if (!(ast instanceof Array)) + return ast; + } + var type = ast[0]; + stack.push(ast); + var gen = user[type]; + if (gen) { + var ret = gen.apply(ast, ast.slice(1)); + if (ret != null) + return ret; + } + gen = walkers[type]; + if (!gen) { + gen = walkers["-other"] || user["-other"]; + } + if (!gen) { + throw new Error("No generator for " + ast); } + var ret = gen.apply(ast, ast.slice(1)); + stack.pop(); + return ret; }; function with_walkers(walkers, cont){ @@ -222,13 +242,12 @@ function ast_walker(ast) { save[i] = user[i]; user[i] = walkers[i]; } - try { return cont(); } - finally { - for (i in save) if (HOP(save, i)) { - if (!save[i]) delete user[i]; - else user[i] = save[i]; - } + var ret = cont(); + for (i in save) if (HOP(save, i)) { + if (!save[i]) delete user[i]; + else user[i] = save[i]; } + return ret; }; return { @@ -358,14 +377,10 @@ function ast_add_scope(ast) { function with_new_scope(cont) { current_scope = new Scope(current_scope); - try { - var ret = current_scope.body = cont(); - ret.scope = current_scope; - return ret; - } - finally { - current_scope = current_scope.parent; - } + var ret = current_scope.body = cont(); + ret.scope = current_scope; + current_scope = current_scope.parent; + return ret; }; function define(name) { @@ -480,8 +495,10 @@ function ast_mangle(ast, do_toplevel) { for (var i in s.names) if (HOP(s.names, i)) { get_mangled(i, true); } - try { var ret = cont(); ret.scope = s; return ret; } - finally { scope = _scope; }; + var ret = cont(); + ret.scope = s; + scope = _scope; + return ret; }; function _vardefs(defs) { @@ -581,59 +598,80 @@ function ast_squeeze(ast, options) { return block; }; - function _lambda(name, args, body) { - return [ this[0], name, args, tighten(body.map(walk)) ]; - }; - - // we get here for blocks that have been already transformed. - // this function does two things: - // 1. discard useless blocks - // 2. join consecutive var declarations - function tighten(statements) { - var cur, prev; - for (var i = 0, ret1 = []; i < statements.length; ++i) { - cur = statements[i]; - if (cur[0] == "block") { - if (cur[1]) { - ret1.push.apply(ret1, cur[1]); + function stats_to_sequences(statements) { + for (var i = 0, ret = [], prev = null; i < statements.length; ++i) { + var st = statements[i]; + if (!prev) { + if (st[0] == "stat") { + prev = [ "seq", st[1] ]; + ret.push([ "stat", prev ]); + } else { + ret.push(st); } + } else if (st[0] == "stat") { + prev.push(st[1]); + } else if (st[0] == "seq") { + prev.push.apply(prev, slice(st, 1)); } else { - ret1.push(cur); + prev = null; + ret.push(st); } } - prev = null; - for (var i = 0, ret2 = []; i < ret1.length; ++i) { - cur = ret1[i]; - if (prev && ((cur[0] == "var" && prev[0] == "var") || - (cur[0] == "const" && prev[0] == "const"))) { - prev[1] = prev[1].concat(cur[1]); + return ret; + }; + + function join_consecutive_vars(statements) { + for (var i = 0, ret = [], prev = null; i < statements.length; ++i) { + var st = statements[i]; + if (prev && ((st[0] == "var" && prev[0] == "var") || + (st[0] == "const" && prev[0] == "const"))) { + prev[1] = prev[1].concat(st[1]); } else { - ret2.push(cur); - prev = cur; + ret.push(st); + prev = st; } } - if (!options.make_seqs) - return ret2; - prev = null; - for (var i = 0, ret3 = []; i < ret2.length; ++i) { - cur = ret2[i]; - if (!prev) { - if (cur[0] == "stat") { - prev = [ "seq", cur[1] ]; - ret3.push([ "stat", prev ]); - } else { - ret3.push(cur); + return ret; + }; + + function discard_unnecessary_blocks(statements) { + for (var i = 0, ret = []; i < statements.length; ++i) { + var st = statements[i]; + if (st[0] == "block") { + if (st[1]) { + ret.push.apply(ret, st[1]); } - } else if (cur[0] == "stat") { - prev.push(cur[1]); - } else if (cur[0] == "seq") { - prev.push.apply(prev, slice(cur, 1)); } else { - prev = null; - ret3.push(cur); + ret.push(st); + } + } + return ret; + }; + + function discard_noop_stats(statements) { + for (var i = 0, ret = []; i < statements.length; ++i) { + var st = statements[i]; + if (st[0] == "stat") { + switch (st[1][0]) { + case "string": + case "name": + case "number": + continue; + } } + ret.push(st); } - return ret3.map(walk); + return ret; + }; + + function tighten(statements) { + statements = statements.map(walk); + if (options.make_seqs) + statements = stats_to_sequences(statements).map(walk); + statements = join_consecutive_vars(statements); + statements = discard_unnecessary_blocks(statements); + statements = discard_noop_stats(statements); + return statements; }; function best_of(ast1, ast2) { @@ -659,6 +697,10 @@ function ast_squeeze(ast, options) { return !b || (b[0] == "block" && (!b[1] || b[1].length == 0)); }; + function _lambda(name, args, body) { + return [ this[0], name, args, tighten(body) ]; + }; + return w.with_walkers({ "sub": function(expr, subscript) { if (subscript[0] == "string") { @@ -716,24 +758,25 @@ function ast_squeeze(ast, options) { return ret; }, "toplevel": function(body) { - return [ "toplevel", tighten(body.map(walk)) ]; + return [ "toplevel", tighten(body) ]; }, "switch": function(expr, body) { var last = body.length - 1; return [ "switch", walk(expr), body.map(function(branch, i){ - var block = tighten(branch[1].map(walk)); + var ca$e = branch[0] ? walk(branch[0]) : null; + var block = tighten(branch[1]); if (i == last && block.length > 0) { var node = block[block.length - 1]; if (node[0] == "break" && !node[1]) block.pop(); } - return [ branch[0] ? walk(branch[0]) : null, block ]; + return [ ca$e, block ]; }) ]; }, "function": _lambda, "defun": _lambda, "block": function(body) { - if (body) return rmblock([ "block", tighten(body.map(walk)) ]); + if (body) return rmblock([ "block", tighten(body) ]); }, "binary": function(op, left, right) { left = walk(left); @@ -769,7 +812,7 @@ function ast_squeeze(ast, options) { /* -----[ re-generate code from the AST ]----- */ -var DOT_CALL_NO_PARENS = jsp.array_to_hash([ +var DOT_CALL_NO_PARENS = array_to_hash([ "name", "array", "string", @@ -801,8 +844,9 @@ function gen_code(ast, beautify) { function with_indent(cont, incr) { if (incr == null) incr = 1; indentation += incr; - try { return cont.apply(null, slice(arguments, 1)); } - finally { indentation -= incr; } + var ret = cont.apply(null, slice(arguments, 1)); + indentation -= incr; + return ret; }; function add_spaces(a) { @@ -996,7 +1040,7 @@ function gen_code(ast, beautify) { var val = make(expr); if (!(HOP(DOT_CALL_NO_PARENS, expr[0]) || expr[0] == "num")) val = "(" + val + ")"; - return operator + (jsp.is_alphanumeric_char(operator.charAt(0)) ? " " : "") + val; + return operator + (is_alphanumeric_char(operator.charAt(0)) ? " " : "") + val; }, "unary-postfix": function(operator, expr) { var val = make(expr); @@ -1156,6 +1200,11 @@ function gen_code(ast, beautify) { }; function make(node) { + // XXX: this helps us deal with generated names in macros. + { + if (!(node instanceof Array)) + return node.toString(); + } var type = node[0]; var gen = generators[type]; if (!gen) @@ -1166,38 +1215,6 @@ function gen_code(ast, beautify) { return make(ast); }; -/* -----[ Utilities ]----- */ - -function repeat_string(str, i) { - if (i <= 0) return ""; - if (i == 1) return str; - var d = repeat_string(str, i >> 1); - d += d; - if (i & 1) d += str; - return d; -}; - -function defaults(args, defs) { - var ret = {}; - if (args === true) - args = {}; - for (var i in defs) if (HOP(defs, i)) { - ret[i] = (args && HOP(args, i)) ? args[i] : defs[i]; - } - return ret; -}; - -function is_identifier(name) { - return /^[a-z_$][a-z0-9_$]*$/i.test(name) && - !HOP(jsp.KEYWORDS_ATOM, name) && - !HOP(jsp.RESERVED_WORDS, name) && - !HOP(jsp.KEYWORDS, name); -}; - -function HOP(obj, prop) { - return Object.prototype.hasOwnProperty.call(obj, prop); -}; - /* -----[ Exports ]----- */ exports.ast_walker = ast_walker; diff --git a/test/macro.js b/test/macro.js new file mode 100755 index 00000000..6a132036 --- /dev/null +++ b/test/macro.js @@ -0,0 +1,21 @@ +#! /usr/bin/env node + +global.sys = require("sys"); +global.INSPECT = INSPECT; +var fs = require("fs"); + +var macrojs = require("../lib/macro-js"); +var pro = require("../lib/process"); +var p = macrojs.createParser(); + +fs.readFile(process.argv[2], function(err, data){ + data = data.toString(); + var ast = p.parse(data); + INSPECT(ast); + // sys.puts(pro.gen_code(pro.ast_squeeze(ast), true)); + sys.puts(pro.gen_code(ast, true)); +}); + +function INSPECT(obj) { + sys.puts(sys.inspect(obj, null, null)); +}; diff --git a/test/macro/defun.js b/test/macro/defun.js new file mode 100644 index 00000000..5814678d --- /dev/null +++ b/test/macro/defun.js @@ -0,0 +1,26 @@ +// -*- espresso -*- + +defmacro ensure_ordered(a:name, b:name) { + var tmp = this.gensym(); + return @if (\a > \b) { + var \tmp = \a; + \a = \b; + \b = \tmp; + }; +} + +defstat defun_region(name:name, (args:name*), b:block) { + var p1 = args[0], p2 = args[1]; + return @function \name (\p1, \p2) { + ensure_ordered(p1, p2); + \@b + }; +} + +// ensure_ordered(foo, bar); + +defun_region foo(start, stop) { + for (var i = start; i <= stop; ++i) { + print(getChar(i)); + } +} diff --git a/test/macro/literal-obj.js b/test/macro/literal-obj.js new file mode 100644 index 00000000..9d9505b4 --- /dev/null +++ b/test/macro/literal-obj.js @@ -0,0 +1,17 @@ +// -*- espresso -*- + +defmacro mkobj(p1, v1, p2, v2, p3, v3) { + var opt = this.gensym(); + var bar = [ "name", "while" ]; + return `{ + \p1: \v1, + \p2: \v2, + \p3: \(this.quote(v3)), + \opt: "And some more", + \bar: "even more" + }; +}; + +mkobj(foo, [ 1, 2, 3 ], + bar, "some string here", + baz, { a: 1, b: 2 }); diff --git a/test/macro/test.js b/test/macro/test.js new file mode 100644 index 00000000..ee8a7f72 --- /dev/null +++ b/test/macro/test.js @@ -0,0 +1,103 @@ +// -*- espresso -*- + +// defmacro test(a:name, b, c:statement) { +// return [ "block", [ [ "binary", "+", [ "name", a ], b ] ].concat(c) ]; +// } + +// test(foo, { parc: "mak" }, { +// var bar = 10; +// check(this.out()); +// }); + +// defmacro order(a:name, b:name) { +// var tmp = this.gensym(); +// return @{ +// if (\a > \b) { +// var \tmp = \a; +// \a = \b; +// \b = \tmp; +// } +// crap(); +// }; +// }; + +defmacro order(a:name, b:name) { + var tmp = this.gensym(); + return `if (\a > \b) { + var \tmp = \a; + \a = \b; + \b = \tmp; + }; +}; + +// defmacro with_orderd(a:name, b:name, c:statement) { +// return @{ +// order(\a, \b); +// \c; +// }; +// }; + +defmacro with_orderd(a:name, b:name, c:statement) { + var tmp = this.gensym(); + return `(function(\a, \b){ + if (\a > \b) { + var \tmp = \a; + \a = \b; + \b = \tmp; + } + \c; + })(\a, \b); +}; + +with_orderd(crap, mak, { + print("Smallest is " + crap); + print("And " + mak + " follows"); + order(mak, crap); + print("Reverse order: " + mak + ", " + crap); +}); + +with_orderd( + foo, bar, + print("order: " + foo + ", " + bar) +); + +// defmacro order(a:name, b:name) { +// var tmp = this.gensym(); +// return `(function(\tmp){ +// \a = \b; +// \b = \tmp; +// })(\a); +// }; + +var foo = 10; +var bar = 20; +order(foo, bar); + +// defmacro qwe (a) { +// var tmp = this.symbol("crapmak"); +// return @{ +// var \tmp = \a; +// ++\tmp; +// }; +// } + +// var a = 5; +// qwe(a); + + +defstat unless(cond, b:statement) { + sys.log("********************************************"); + INSPECT(cond); + return @if (!\cond) \b; +} + +unless (foo + bar < 10) { + crap(); + mak(); +} + +(function(){ + unless (foo + bar < 10) unless (bar) return @if (baz) { + crap(); + } +})();