added and= and or= to the language.

This commit is contained in:
Jeremy Ashkenas 2010-07-24 22:36:50 -07:00
parent 88847df70b
commit f9dff6ffc4
6 changed files with 36 additions and 54 deletions

View File

@ -1,5 +1,5 @@
(function(){
var ASSIGNED, CALLABLE, CODE, COFFEE_ALIASES, COFFEE_KEYWORDS, COMMENT, CONVERSIONS, HALF_ASSIGNMENTS, HEREDOC, HEREDOC_INDENT, IDENTIFIER, INTERPOLATION, JS_CLEANER, JS_FORBIDDEN, JS_KEYWORDS, LAST_DENT, LAST_DENTS, LINE_BREAK, Lexer, MULTILINER, MULTI_DENT, NEXT_CHARACTER, NOT_REGEX, NO_NEWLINE, NUMBER, OPERATOR, REGEX_END, REGEX_ESCAPE, REGEX_INTERPOLATION, REGEX_START, RESERVED, Rewriter, STRING_NEWLINES, WHITESPACE, _a, _b, _c, compact, count, helpers, include, starts;
var ASSIGNED, CALLABLE, CODE, COFFEE_ALIASES, COFFEE_KEYWORDS, COMMENT, CONVERSIONS, HEREDOC, HEREDOC_INDENT, IDENTIFIER, INTERPOLATION, JS_CLEANER, JS_FORBIDDEN, JS_KEYWORDS, LAST_DENT, LAST_DENTS, LINE_BREAK, Lexer, MULTILINER, MULTI_DENT, NEXT_CHARACTER, NOT_REGEX, NO_NEWLINE, NUMBER, OPERATOR, REGEX_END, REGEX_ESCAPE, REGEX_INTERPOLATION, REGEX_START, RESERVED, Rewriter, STRING_NEWLINES, WHITESPACE, _a, _b, _c, compact, count, helpers, include, starts;
var __slice = Array.prototype.slice;
if (typeof process !== "undefined" && process !== null) {
_a = require('./rewriter');
@ -105,9 +105,6 @@
if (include(COFFEE_ALIASES, id)) {
tag = (id = CONVERSIONS[id]);
}
if (this.prev() && this.prev()[0] === 'ASSIGN' && include(HALF_ASSIGNMENTS, tag)) {
return this.tagHalfAssignment(tag);
}
}
this.token(tag, id);
if (close_index) {
@ -299,7 +296,7 @@
return true;
};
Lexer.prototype.literalToken = function() {
var match, prevSpaced, space, tag, value;
var _d, match, prevSpaced, space, tag, value;
match = this.chunk.match(OPERATOR);
value = match && match[1];
space = match && match[2];
@ -307,10 +304,16 @@
this.tagParameters();
}
value = value || this.chunk.substr(0, 1);
this.i += value.length;
prevSpaced = this.prev() && this.prev().spaced;
tag = value;
if (value === '=' && include(JS_FORBIDDEN, this.value)) {
this.assignmentError();
if (value === '=') {
if (include(JS_FORBIDDEN, this.value())) {
this.assignmentError();
}
if (('or' === (_d = this.value()) || 'and' === _d)) {
return this.tag(1, CONVERSIONS[this.value()] + '=');
}
}
if (value === ';') {
tag = 'TERMINATOR';
@ -329,10 +332,6 @@
}
}
}
this.i += value.length;
if (space && prevSpaced && this.prev()[0] === 'ASSIGN' && include(HALF_ASSIGNMENTS, tag)) {
return this.tagHalfAssignment(tag);
}
this.token(tag, value);
return true;
};
@ -371,15 +370,6 @@
}
return doc.replace(MULTILINER, "\\n").replace(new RegExp(options.quote, 'g'), ("\\" + options.quote));
};
Lexer.prototype.tagHalfAssignment = function(tag) {
var last;
if (tag === 'OP?') {
tag = '?';
}
last = this.tokens.pop();
this.tokens.push([("" + tag + "="), ("" + tag + "="), last[2]]);
return true;
};
Lexer.prototype.tagParameters = function() {
var _d, i, tok;
if (this.tag() !== ')') {
@ -616,12 +606,12 @@
NOT_REGEX = ['NUMBER', 'REGEX', '++', '--', 'FALSE', 'NULL', 'TRUE', ']'];
CALLABLE = ['IDENTIFIER', 'SUPER', ')', ']', '}', 'STRING', '@', 'THIS', '?', '::'];
LINE_BREAK = ['INDENT', 'OUTDENT', 'TERMINATOR'];
HALF_ASSIGNMENTS = ['-', '+', '/', '*', '%', '||', '&&', '?', 'OP?'];
CONVERSIONS = {
'and': '&&',
'or': '||',
'is': '==',
'isnt': '!=',
'not': '!'
'not': '!',
'===': '=='
};
})();

View File

@ -30,7 +30,7 @@ lexer = new Lexer
# Compile a string of CoffeeScript code to JavaScript, using the Coffee/Jison
# compiler.
exports.compile = compile = (code, options) ->
options ||= {}
options or= {}
try
(parser.parse lexer.tokenize code).compile options
catch err

View File

@ -156,7 +156,7 @@ printTokens = (tokens) ->
parseOptions = ->
optionParser = new optparse.OptionParser SWITCHES, BANNER
o = options = optionParser.parse(process.argv[2...process.argv.length])
options.compile ||= !!o.output
options.compile or= !!o.output
options.run = not (o.compile or o.print or o.lint)
options.print = !! (o.print or (o.eval or o.stdio and o.compile))
sources = options.arguments

View File

@ -100,7 +100,6 @@ exports.Lexer = class Lexer
@identifierError id
unless forcedIdentifier
tag = id = CONVERSIONS[id] if include COFFEE_ALIASES, id
return @tagHalfAssignment tag if @prev() and @prev()[0] is 'ASSIGN' and include HALF_ASSIGNMENTS, tag
@token tag, id
@token ']', ']' if close_index
true
@ -258,10 +257,14 @@ exports.Lexer = class Lexer
value = match and match[1]
space = match and match[2]
@tagParameters() if value and value.match CODE
value ||= @chunk.substr 0, 1
value or= @chunk.substr 0, 1
@i += value.length
prevSpaced = @prev() and @prev().spaced
tag = value
@assignmentError() if value is '=' and include JS_FORBIDDEN, @value
if value is '='
@assignmentError() if include JS_FORBIDDEN, @value()
if @value() in ['or', 'and']
return @tag 1, CONVERSIONS[@value()] + '='
if value is ';'
tag = 'TERMINATOR'
else if value is '?' and prevSpaced
@ -273,8 +276,6 @@ exports.Lexer = class Lexer
tag = 'INDEX_START'
@tag 1, 'INDEX_SOAK' if @tag() is '?'
@tag 1, 'INDEX_PROTO' if @tag() is '::'
@i += value.length
return @tagHalfAssignment tag if space and prevSpaced and @prev()[0] is 'ASSIGN' and include HALF_ASSIGNMENTS, tag
@token tag, value
true
@ -309,13 +310,6 @@ exports.Lexer = class Lexer
doc.replace(MULTILINER, "\\n")
.replace(new RegExp(options.quote, 'g'), "\\$options.quote")
# Tag a half assignment.
tagHalfAssignment: (tag) ->
tag = '?' if tag is 'OP?'
last = @tokens.pop()
@tokens.push ["$tag=", "$tag=", last[2]]
true
# A source of ambiguity in our grammar used to be parameter lists in function
# definitions versus argument lists in function calls. Walk backwards, tagging
# parameters specially in order to make things easier for the parser.
@ -351,7 +345,7 @@ exports.Lexer = class Lexer
# contents of the string. This method allows us to have strings within
# interpolations within strings, ad infinitum.
balancedString: (str, delimited, options) ->
options ||= {}
options or= {}
slash = delimited[0][0] is '/'
levels = []
i = 0
@ -388,7 +382,7 @@ exports.Lexer = class Lexer
# new Lexer, tokenize the interpolated contents, and merge them into the
# token stream.
interpolateString: (str, options) ->
options ||= {}
options or= {}
if str.length < 3 or not starts str, '"'
@token 'STRING', str
else
@ -556,9 +550,6 @@ CALLABLE = ['IDENTIFIER', 'SUPER', ')', ']', '}', 'STRING', '@', 'THIS', '?', ':
# avoid an ambiguity in the grammar.
LINE_BREAK = ['INDENT', 'OUTDENT', 'TERMINATOR']
# Half-assignments...
HALF_ASSIGNMENTS = ['-', '+', '/', '*', '%', '||', '&&', '?', 'OP?']
# Conversions from CoffeeScript operators into JavaScript ones.
CONVERSIONS = {
'and': '&&'
@ -566,4 +557,5 @@ CONVERSIONS = {
'is': '=='
'isnt': '!='
'not': '!'
'===': '=='
}

View File

@ -112,7 +112,7 @@ exports.BaseNode = class BaseNode
# `toString` representation of the node, for inspecting the parse tree.
# This is what `coffee --nodes` prints out.
toString: (idt, override) ->
idt ||= ''
idt or= ''
children = (child.toString idt + TAB for child in @collectChildren()).join('')
'\n' + idt + (override or @class) + children
@ -193,7 +193,7 @@ exports.Expressions = class Expressions extends BaseNode
# An **Expressions** is the only node that can serve as the root.
compile: (o) ->
o ||= {}
o or= {}
if o.scope then super(o) else @compileRoot(o)
compileNode: (o) ->
@ -354,7 +354,7 @@ exports.ValueNode = class ValueNode extends BaseNode
only = del o, 'onlyFirst'
op = del o, 'operation'
props = if only then @properties[0...@properties.length - 1] else @properties
o.chainRoot ||= this
o.chainRoot or= this
baseline = @base.compile o
baseline = "($baseline)" if @hasProperties() and (@base instanceof ObjectNode or @isNumber())
complete = @last = baseline
@ -502,7 +502,7 @@ exports.AccessorNode = class AccessorNode extends BaseNode
compileNode: (o) ->
name = @name.compile o
o.chainRoot.wrapped ||= @soakNode
o.chainRoot.wrapped or= @soakNode
namePart = if name.match(IS_STRING) then "[$name]" else ".$name"
@prototype + namePart
@ -518,7 +518,7 @@ exports.IndexNode = class IndexNode extends BaseNode
@index = index
compileNode: (o) ->
o.chainRoot.wrapped ||= @soakNode
o.chainRoot.wrapped or= @soakNode
idx = @index.compile o
prefix = if @proto then '.prototype' else ''
"$prefix[$idx]"
@ -570,7 +570,7 @@ exports.RangeNode = class RangeNode extends BaseNode
[from, to] = [parseInt(@fromNum, 10), parseInt(@toNum, 10)]
idx = del o, 'index'
step = del o, 'step'
step &&= "$idx += ${step.compile(o)}"
step and= "$idx += ${step.compile(o)}"
if from <= to
"$idx = $from; $idx <$@equals $to; ${step or "$idx++"}"
else
@ -721,8 +721,8 @@ exports.ClassNode = class ClassNode extends BaseNode
continue
if func instanceof CodeNode and func.bound
func.bound = false
constScope ||= new Scope(o.scope, constructor.body, constructor)
me ||= constScope.freeVariable()
constScope or= new Scope(o.scope, constructor.body, constructor)
me or= constScope.freeVariable()
pname = pvar.compile(o)
constructor.body.push new ReturnNode literal 'this' if constructor.body.empty()
constructor.body.unshift literal "this.${pname} = function(){ return ${className}.prototype.${pname}.apply($me, arguments); }"
@ -902,7 +902,7 @@ exports.CodeNode = class CodeNode extends BaseNode
traverseChildren: (crossScope, func) -> super(crossScope, func) if crossScope
toString: (idt) ->
idt ||= ''
idt or= ''
children = (child.toString(idt + TAB) for child in @collectChildren()).join('')
"\n$idt$children"
@ -1385,7 +1385,7 @@ exports.IfNode = class IfNode extends BaseNode
# The **IfNode** only compiles into a statement if either of its bodies needs
# to be a statement. Otherwise a ternary is safe.
isStatement: ->
@statement ||= !!(@tags.statement or @bodyNode().isStatement() or (@elseBody and @elseBodyNode().isStatement()))
@statement or= !!(@tags.statement or @bodyNode().isStatement() or (@elseBody and @elseBodyNode().isStatement()))
compileCondition: (o) ->
(cond.compile(o) for cond in flatten([@condition])).join(' || ')
@ -1395,8 +1395,8 @@ exports.IfNode = class IfNode extends BaseNode
makeReturn: ->
if @isStatement()
@body &&= @ensureExpressions(@body.makeReturn())
@elseBody &&= @ensureExpressions(@elseBody.makeReturn())
@body and= @ensureExpressions(@body.makeReturn())
@elseBody and= @ensureExpressions(@elseBody.makeReturn())
this
else
new ReturnNode this

View File

@ -198,7 +198,7 @@ exports.Rewriter = class Rewriter
@scanTokens (prev, token, post, i) =>
for pair in pairs
[open, close] = pair
levels[open] ||= 0
levels[open] or= 0
if token[0] is open
openLine[open] = token[2] if levels[open] == 0
levels[open] += 1