added the ability to print the parse tree

This commit is contained in:
Jeremy Ashkenas 2010-02-11 23:11:05 -05:00
parent 950d1199c2
commit 7c01bba4f4
6 changed files with 92 additions and 10 deletions

View File

@ -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');

View File

@ -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();

View File

@ -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();

View File

@ -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. ----------------

View File

@ -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()

View File

@ -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