From 7c01bba4f43d222c39d18660719fda4235bc74ee Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 11 Feb 2010 23:11:05 -0500 Subject: [PATCH] added the ability to print the parse tree --- lib/coffee_script/coffee-script.js | 4 ++++ lib/coffee_script/command_line.js | 14 +++++++---- lib/coffee_script/nodes.js | 38 ++++++++++++++++++++++++++++++ src/coffee-script.coffee | 4 ++++ src/command_line.coffee | 8 ++++--- src/nodes.coffee | 34 ++++++++++++++++++++++++-- 6 files changed, 92 insertions(+), 10 deletions(-) diff --git a/lib/coffee_script/coffee-script.js b/lib/coffee_script/coffee-script.js index 746930dd..c3fa0eee 100644 --- a/lib/coffee_script/coffee-script.js +++ b/lib/coffee_script/coffee-script.js @@ -33,6 +33,10 @@ exports.tokenize = function tokenize(code) { return lexer.tokenize(code); }; + // Just the nodes. + exports.tree = function tree(code) { + return parser.parse(lexer.tokenize(code)); + }; //---------- Below this line is obsolete, for the Ruby compiler. ---------------- // Executes the `coffee` Ruby program to convert from CoffeeScript to JavaScript. path = require('path'); diff --git a/lib/coffee_script/command_line.js b/lib/coffee_script/command_line.js index 4292ec07..0791f64a 100644 --- a/lib/coffee_script/command_line.js +++ b/lib/coffee_script/command_line.js @@ -4,7 +4,7 @@ posix = require('posix'); coffee = require('coffee-script'); BANNER = "coffee compiles CoffeeScript source files into JavaScript.\n\nUsage:\n coffee path/to/script.coffee"; - SWITCHES = [['-i', '--interactive', 'run an interactive CoffeeScript REPL'], ['-r', '--run', 'compile and run a CoffeeScript'], ['-o', '--output [DIR]', 'set the directory for compiled JavaScript'], ['-w', '--watch', 'watch scripts for changes, and recompile'], ['-p', '--print', 'print the compiled JavaScript to stdout'], ['-l', '--lint', 'pipe the compiled JavaScript through JSLint'], ['-e', '--eval', 'compile a cli scriptlet or read from stdin'], ['-t', '--tokens', 'print the tokens that the lexer produces'], ['-n', '--no-wrap', 'raw output, no function safety wrapper'], ['-g', '--globals', 'attach all top-level variables as globals'], ['-v', '--version', 'display CoffeeScript version'], ['-h', '--help', 'display this help message']]; + SWITCHES = [['-i', '--interactive', 'run an interactive CoffeeScript REPL'], ['-r', '--run', 'compile and run a CoffeeScript'], ['-o', '--output [DIR]', 'set the directory for compiled JavaScript'], ['-w', '--watch', 'watch scripts for changes, and recompile'], ['-p', '--print', 'print the compiled JavaScript to stdout'], ['-l', '--lint', 'pipe the compiled JavaScript through JSLint'], ['-e', '--eval', 'compile a cli scriptlet or read from stdin'], ['-t', '--tokens', 'print the tokens that the lexer produces'], ['--tree', 'print the parse tree that Jison produces'], ['-n', '--no-wrap', 'raw output, no function safety wrapper'], ['-g', '--globals', 'attach all top-level variables as globals'], ['-v', '--version', 'display CoffeeScript version'], ['-h', '--help', 'display this help message']]; WATCH_INTERVAL = 0.5; // The CommandLine handles all of the functionality of the `coffee` utility. exports.run = function run() { @@ -53,16 +53,17 @@ } opts = this.options; return posix.cat(source).addCallback(function(code) { - var js; if (opts.tokens) { return puts(coffee.tokenize(code).join(' ')); } - js = coffee.compile(code); + if (opts.tree) { + return puts(coffee.tree(code).toString()); + } if (opts.run) { - return eval(js); + return eval(coffee.compile(code)); } if (opts.print) { - return puts(js); + return puts(coffee.compile(code)); } return exports.compile_scripts(); }); @@ -98,6 +99,9 @@ oparser.add('tokens', function() { return opts.tokens = true; }); + oparser.add('tree', function() { + return opts.tree = true; + }); oparser.add('help', (function(__this) { var __func = function() { return this.usage(); diff --git a/lib/coffee_script/nodes.js b/lib/coffee_script/nodes.js index ebba6bb9..4289be6c 100644 --- a/lib/coffee_script/nodes.js +++ b/lib/coffee_script/nodes.js @@ -185,6 +185,19 @@ } return false; }; + // toString representation of the node, for inspecting the parse tree. + Node.prototype.toString = function toString(idt) { + var __a, __b, __c, child; + idt = idt || ''; + return (this.type || 'anon') + "\n" + ((function() { + __a = []; __b = this.children; + for (__c = 0; __c < __b.length; __c++) { + child = __b[__c]; + __a.push(idt + TAB + child.toString(idt + TAB)); + } + return __a; + }).call(this)); + }; // Default implementations of the common node methods. Node.prototype.unwrap = function unwrap() { return this; @@ -201,6 +214,7 @@ }; // A collection of nodes, each one representing an expression. Expressions = (exports.Expressions = inherit(Node, { + type: 'Expressions', constructor: function constructor(nodes) { this.children = (this.expressions = compact(flatten(nodes))); return this; @@ -310,6 +324,7 @@ // Literals are static values that can be passed through directly into // JavaScript without translation, eg.: strings, numbers, true, false, null... LiteralNode = (exports.LiteralNode = inherit(Node, { + type: 'Literal', constructor: function constructor(value) { this.value = value; return this; @@ -329,6 +344,7 @@ LiteralNode.prototype.is_statement_only = LiteralNode.prototype.is_statement; // Return an expression, or wrap it in a closure and return it. ReturnNode = (exports.ReturnNode = inherit(Node, { + type: 'Return', constructor: function constructor(expression) { this.children = [(this.expression = expression)]; return this; @@ -345,6 +361,7 @@ statement(ReturnNode, true); // A value, indexed or dotted into, or vanilla. ValueNode = (exports.ValueNode = inherit(Node, { + type: 'Value', SOAK: " == undefined ? undefined : ", constructor: function constructor(base, properties) { this.children = flatten((this.base = base), (this.properties = (properties || []))); @@ -413,6 +430,7 @@ // Pass through CoffeeScript comments into JavaScript comments at the // same position. CommentNode = (exports.CommentNode = inherit(Node, { + type: 'Comment', constructor: function constructor(lines) { this.lines = lines; return this; @@ -427,6 +445,7 @@ // Node for a function invocation. Takes care of converting super() calls into // calls against the prototype's function of the same name. CallNode = (exports.CallNode = inherit(Node, { + type: 'Call', constructor: function constructor(variable, args) { this.children = flatten([(this.variable = variable), (this.args = (args || []))]); this.prefix = ''; @@ -501,6 +520,7 @@ // Node to extend an object's prototype with an ancestor object. // After goog.inherits from the Closure Library. ExtendsNode = (exports.ExtendsNode = inherit(Node, { + type: 'Extends', constructor: function constructor(child, parent) { this.children = [(this.child = child), (this.parent = parent)]; return this; @@ -518,6 +538,7 @@ // A dotted accessor into a part of a value, or the :: shorthand for // an accessor into the object's prototype. AccessorNode = (exports.AccessorNode = inherit(Node, { + type: 'Accessor', constructor: function constructor(name, tag) { this.children = [(this.name = name)]; this.prototype = tag === 'prototype'; @@ -530,6 +551,7 @@ })); // An indexed accessor into a part of an array or object. IndexNode = (exports.IndexNode = inherit(Node, { + type: 'Index', constructor: function constructor(index) { this.children = [(this.index = index)]; return this; @@ -540,6 +562,7 @@ })); // A this-reference, using '@'. ThisNode = (exports.ThisNode = inherit(Node, { + type: 'This', constructor: function constructor(property) { this.property = property || null; return this; @@ -551,6 +574,7 @@ // A range literal. Ranges can be used to extract portions (slices) of arrays, // or to specify a range for list comprehensions. RangeNode = (exports.RangeNode = inherit(Node, { + type: 'Range', constructor: function constructor(from, to, exclusive) { this.children = [(this.from = from), (this.to = to)]; this.exclusive = !!exclusive; @@ -594,6 +618,7 @@ // specifies the index of the end of the slice (just like the first parameter) // is the index of the beginning. SliceNode = (exports.SliceNode = inherit(Node, { + type: 'Slice', constructor: function constructor(range) { this.children = [(this.range = range)]; return this; @@ -608,6 +633,7 @@ })); // An object literal. ObjectNode = (exports.ObjectNode = inherit(Node, { + type: 'Object', constructor: function constructor(props) { this.objects = (this.properties = props || []); return this; @@ -652,6 +678,7 @@ })); // An array literal. ArrayNode = (exports.ArrayNode = inherit(Node, { + type: 'Array', constructor: function constructor(objects) { this.children = (this.objects = objects || []); return this; @@ -706,6 +733,7 @@ }); // Setting the value of a local variable, or the value of an object property. AssignNode = (exports.AssignNode = inherit(Node, { + type: 'Assign', PROTO_ASSIGN: /^(\S+)\.prototype/, LEADING_DOT: /^\.(prototype\.)?/, constructor: function constructor(variable, value, context) { @@ -809,6 +837,7 @@ // A function definition. The only node that creates a new Scope. // A CodeNode does not have any children -- they're within the new scope. CodeNode = (exports.CodeNode = inherit(Node, { + type: 'Code', constructor: function constructor(params, body, tag) { this.params = params; this.body = body; @@ -854,6 +883,7 @@ // A splat, either as a parameter to a function, an argument to a call, // or in a destructuring assignment. SplatNode = (exports.SplatNode = inherit(Node, { + type: 'Splat', constructor: function constructor(name) { this.children = [(this.name = name)]; return this; @@ -874,6 +904,7 @@ // A while loop, the only sort of low-level loop exposed by CoffeeScript. From // it, all other loops can be manufactured. WhileNode = (exports.WhileNode = inherit(Node, { + type: 'While', constructor: function constructor(condition, body) { this.children = [(this.condition = condition), (this.body = body)]; return this; @@ -906,6 +937,7 @@ // Simple Arithmetic and logical operations. Performs some conversion from // CoffeeScript operations into their JavaScript equivalents. OpNode = (exports.OpNode = inherit(Node, { + type: 'Op', CONVERSIONS: { '==': '===', '!=': '!==', @@ -989,6 +1021,7 @@ })); // A try/catch/finally block. TryNode = (exports.TryNode = inherit(Node, { + type: 'Try', constructor: function constructor(attempt, error, recovery, ensure) { this.children = [(this.attempt = attempt), (this.recovery = recovery), (this.ensure = ensure)]; this.error = error; @@ -1009,6 +1042,7 @@ statement(TryNode); // Throw an exception. ThrowNode = (exports.ThrowNode = inherit(Node, { + type: 'Throw', constructor: function constructor(expression) { this.children = [(this.expression = expression)]; return this; @@ -1020,6 +1054,7 @@ statement(ThrowNode, true); // Check an expression for existence (meaning not null or undefined). ExistenceNode = (exports.ExistenceNode = inherit(Node, { + type: 'Existence', constructor: function constructor(expression) { this.children = [(this.expression = expression)]; return this; @@ -1042,6 +1077,7 @@ }; // An extra set of parentheses, specified explicitly in the source. ParentheticalNode = (exports.ParentheticalNode = inherit(Node, { + type: 'Paren', constructor: function constructor(expressions) { this.children = [(this.expressions = expressions)]; return this; @@ -1061,6 +1097,7 @@ // of the comprehenion. Unlike Python array comprehensions, it's able to pass // the current index of the loop as a second parameter. ForNode = (exports.ForNode = inherit(Node, { + type: 'For', constructor: function constructor(body, source, name, index) { var __a; this.body = body; @@ -1161,6 +1198,7 @@ // Single-expression IfNodes are compiled into ternary operators if possible, // because ternaries are first-class returnable assignable expressions. IfNode = (exports.IfNode = inherit(Node, { + type: 'If', constructor: function constructor(condition, body, else_body, tags) { this.condition = condition; this.body = body && body.unwrap(); diff --git a/src/coffee-script.coffee b/src/coffee-script.coffee index cc7de8b6..617300e9 100644 --- a/src/coffee-script.coffee +++ b/src/coffee-script.coffee @@ -27,6 +27,10 @@ exports.compile: (code) -> exports.tokenize: (code) -> lexer.tokenize code +# Just the nodes. +exports.tree: (code) -> + parser.parse lexer.tokenize code + #---------- Below this line is obsolete, for the Ruby compiler. ---------------- diff --git a/src/command_line.coffee b/src/command_line.coffee index 307c0f2b..89340338 100644 --- a/src/command_line.coffee +++ b/src/command_line.coffee @@ -18,6 +18,7 @@ SWITCHES: [ ['-l', '--lint', 'pipe the compiled JavaScript through JSLint'] ['-e', '--eval', 'compile a cli scriptlet or read from stdin'] ['-t', '--tokens', 'print the tokens that the lexer produces'] + [ '--tree', 'print the parse tree that Jison produces'] ['-n', '--no-wrap', 'raw output, no function safety wrapper'] ['-g', '--globals', 'attach all top-level variables as globals'] ['-v', '--version', 'display CoffeeScript version'] @@ -62,9 +63,9 @@ exports.compile_scripts: -> opts: @options posix.cat(source).addCallback (code) -> return puts coffee.tokenize(code).join(' ') if opts.tokens - js: coffee.compile code - return eval js if opts.run - return puts js if opts.print + return puts coffee.tree(code).toString() if opts.tree + return eval coffee.compile code if opts.run + return puts coffee.compile code if opts.print exports.compile_scripts() @@ -82,6 +83,7 @@ exports.parse_options: -> oparser.add 'lint', -> opts.lint: true oparser.add 'eval', -> opts.eval: true oparser.add 'tokens', -> opts.tokens: true + oparser.add 'tree', -> opts.tree: true oparser.add 'help', => @usage() oparser.add 'version', => @version() diff --git a/src/nodes.coffee b/src/nodes.coffee index 31672e5d..56142d67 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -58,8 +58,8 @@ inherit: (parent, props) -> # Mark a node as a statement, or a statement only. statement: (klass, only) -> - klass::is_statement: -> true - (klass::is_statement_only: -> true) if only + klass::is_statement: -> true + (klass::is_statement_only: -> true) if only # The abstract base class for all CoffeeScript nodes. # All nodes are implement a "compile_node" method, which performs the @@ -103,6 +103,11 @@ Node::contains: (block) -> return true if node instanceof Node and node.contains block false +# toString representation of the node, for inspecting the parse tree. +Node::toString: (idt) -> + idt ||= '' + (@type || 'anon') + "\n" + (idt + TAB + child.toString(idt + TAB) for child in @children) + # Default implementations of the common node methods. Node::unwrap: -> this Node::children: [] @@ -112,6 +117,7 @@ Node::top_sensitive: -> false # A collection of nodes, each one representing an expression. Expressions: exports.Expressions: inherit Node, { + type: 'Expressions' constructor: (nodes) -> @children: @expressions: compact flatten nodes @@ -193,6 +199,7 @@ statement Expressions # Literals are static values that can be passed through directly into # JavaScript without translation, eg.: strings, numbers, true, false, null... LiteralNode: exports.LiteralNode: inherit Node, { + type: 'Literal' constructor: (value) -> @value: value @@ -214,6 +221,7 @@ LiteralNode::is_statement_only: LiteralNode::is_statement # Return an expression, or wrap it in a closure and return it. ReturnNode: exports.ReturnNode: inherit Node, { + type: 'Return' constructor: (expression) -> @children: [@expression: expression] @@ -229,6 +237,7 @@ statement ReturnNode, true # A value, indexed or dotted into, or vanilla. ValueNode: exports.ValueNode: inherit Node, { + type: 'Value' SOAK: " == undefined ? undefined : " @@ -294,6 +303,7 @@ ValueNode: exports.ValueNode: inherit Node, { # Pass through CoffeeScript comments into JavaScript comments at the # same position. CommentNode: exports.CommentNode: inherit Node, { + type: 'Comment' constructor: (lines) -> @lines: lines @@ -310,6 +320,7 @@ statement CommentNode # Node for a function invocation. Takes care of converting super() calls into # calls against the prototype's function of the same name. CallNode: exports.CallNode: inherit Node, { + type: 'Call' constructor: (variable, args) -> @children: flatten [@variable: variable, @args: (args or [])] @@ -364,6 +375,7 @@ CallNode: exports.CallNode: inherit Node, { # Node to extend an object's prototype with an ancestor object. # After goog.inherits from the Closure Library. ExtendsNode: exports.ExtendsNode: inherit Node, { + type: 'Extends' constructor: (child, parent) -> @children: [@child: child, @parent: parent] @@ -387,6 +399,7 @@ statement ExtendsNode # A dotted accessor into a part of a value, or the :: shorthand for # an accessor into the object's prototype. AccessorNode: exports.AccessorNode: inherit Node, { + type: 'Accessor' constructor: (name, tag) -> @children: [@name: name] @@ -401,6 +414,7 @@ AccessorNode: exports.AccessorNode: inherit Node, { # An indexed accessor into a part of an array or object. IndexNode: exports.IndexNode: inherit Node, { + type: 'Index' constructor: (index) -> @children: [@index: index] @@ -413,6 +427,7 @@ IndexNode: exports.IndexNode: inherit Node, { # A this-reference, using '@'. ThisNode: exports.ThisNode: inherit Node, { + type: 'This' constructor: (property) -> @property: property or null @@ -426,6 +441,7 @@ ThisNode: exports.ThisNode: inherit Node, { # A range literal. Ranges can be used to extract portions (slices) of arrays, # or to specify a range for list comprehensions. RangeNode: exports.RangeNode: inherit Node, { + type: 'Range' constructor: (from, to, exclusive) -> @children: [@from: from, @to: to] @@ -464,6 +480,7 @@ RangeNode: exports.RangeNode: inherit Node, { # specifies the index of the end of the slice (just like the first parameter) # is the index of the beginning. SliceNode: exports.SliceNode: inherit Node, { + type: 'Slice' constructor: (range) -> @children: [@range: range] @@ -479,6 +496,7 @@ SliceNode: exports.SliceNode: inherit Node, { # An object literal. ObjectNode: exports.ObjectNode: inherit Node, { + type: 'Object' constructor: (props) -> @objects: @properties: props or [] @@ -503,6 +521,7 @@ ObjectNode: exports.ObjectNode: inherit Node, { # An array literal. ArrayNode: exports.ArrayNode: inherit Node, { + type: 'Array' constructor: (objects) -> @children: @objects: objects or [] @@ -549,6 +568,7 @@ ClosureNode: exports.ClosureNode: { # Setting the value of a local variable, or the value of an object property. AssignNode: exports.AssignNode: inherit Node, { + type: 'Assign' PROTO_ASSIGN: /^(\S+)\.prototype/ LEADING_DOT: /^\.(prototype\.)?/ @@ -621,6 +641,7 @@ AssignNode: exports.AssignNode: inherit Node, { # A function definition. The only node that creates a new Scope. # A CodeNode does not have any children -- they're within the new scope. CodeNode: exports.CodeNode: inherit Node, { + type: 'Code' constructor: (params, body, tag) -> @params: params @@ -658,6 +679,7 @@ CodeNode: exports.CodeNode: inherit Node, { # A splat, either as a parameter to a function, an argument to a call, # or in a destructuring assignment. SplatNode: exports.SplatNode: inherit Node, { + type: 'Splat' constructor: (name) -> @children: [@name: name] @@ -679,6 +701,7 @@ SplatNode: exports.SplatNode: inherit Node, { # A while loop, the only sort of low-level loop exposed by CoffeeScript. From # it, all other loops can be manufactured. WhileNode: exports.WhileNode: inherit Node, { + type: 'While' constructor: (condition, body) -> @children:[@condition: condition, @body: body] @@ -710,6 +733,7 @@ statement WhileNode # Simple Arithmetic and logical operations. Performs some conversion from # CoffeeScript operations into their JavaScript equivalents. OpNode: exports.OpNode: inherit Node, { + type: 'Op' CONVERSIONS: { '==': '===' @@ -771,6 +795,7 @@ OpNode: exports.OpNode: inherit Node, { # A try/catch/finally block. TryNode: exports.TryNode: inherit Node, { + type: 'Try' constructor: (attempt, error, recovery, ensure) -> @children: [@attempt: attempt, @recovery: recovery, @ensure: ensure] @@ -791,6 +816,7 @@ statement TryNode # Throw an exception. ThrowNode: exports.ThrowNode: inherit Node, { + type: 'Throw' constructor: (expression) -> @children: [@expression: expression] @@ -805,6 +831,7 @@ statement ThrowNode, true # Check an expression for existence (meaning not null or undefined). ExistenceNode: exports.ExistenceNode: inherit Node, { + type: 'Existence' constructor: (expression) -> @children: [@expression: expression] @@ -822,6 +849,7 @@ ExistenceNode.compile_test: (o, variable) -> # An extra set of parentheses, specified explicitly in the source. ParentheticalNode: exports.ParentheticalNode: inherit Node, { + type: 'Paren' constructor: (expressions) -> @children: [@expressions: expressions] @@ -840,6 +868,7 @@ ParentheticalNode: exports.ParentheticalNode: inherit Node, { # of the comprehenion. Unlike Python array comprehensions, it's able to pass # the current index of the loop as a second parameter. ForNode: exports.ForNode: inherit Node, { + type: 'For' constructor: (body, source, name, index) -> @body: body @@ -914,6 +943,7 @@ statement ForNode # Single-expression IfNodes are compiled into ternary operators if possible, # because ternaries are first-class returnable assignable expressions. IfNode: exports.IfNode: inherit Node, { + type: 'If' constructor: (condition, body, else_body, tags) -> @condition: condition