diff --git a/index.html b/index.html index 7ed405f1..a39100ee 100644 --- a/index.html +++ b/index.html @@ -1912,6 +1912,10 @@ task('build:parserVim CoffeeScript — which adds Vim syntax highlighting and indentation support. +
  • + nfiniteset's CoffeeScript.mode + — a syntax mode that provides highlighting for SubEthaEdit and Coda. +
  • wavded's gedit-coffeescript — a CoffeeScript syntax highlighter for the gedit text editor. diff --git a/lib/browser.js b/lib/browser.js index 7d78ce38..1b462e7d 100644 --- a/lib/browser.js +++ b/lib/browser.js @@ -12,7 +12,7 @@ options.bare = true; return Function(CoffeeScript.compile(code, options))(); }; - if (typeof window == "undefined" || window === null) { + if (typeof window == "undefined" || window == null) { return; } CoffeeScript.load = function(url, options) { diff --git a/lib/coffee-script.js b/lib/coffee-script.js index 309e8d14..2bd8df1f 100755 --- a/lib/coffee-script.js +++ b/lib/coffee-script.js @@ -42,7 +42,7 @@ } }; exports.run = function(code, options) { - var root; + var Module, root; root = module; while (root.parent) { root = root.parent; @@ -51,6 +51,10 @@ if (root.moduleCache) { root.moduleCache = {}; } + if (process.binding('natives').module) { + Module = require('module').Module; + root.paths = Module._nodeModulePaths(path.dirname(options.filename)); + } if (path.extname(root.filename) !== '.coffee' || require.extensions) { return root._compile(compile(code, options), root.filename); } else { diff --git a/lib/lexer.js b/lib/lexer.js index 945f11d7..8fdee451 100644 --- a/lib/lexer.js +++ b/lib/lexer.js @@ -459,7 +459,7 @@ Lexer.prototype.balancedString = function(str, end) { var i, letter, prev, stack, _ref; stack = [end]; - for (i = 1, _ref = str.length; (1 <= _ref ? i < _ref : i > _ref); (1 <= _ref ? i += 1 : i -= 1)) { + for (i = 1, _ref = str.length; 1 <= _ref ? i < _ref : i > _ref; 1 <= _ref ? i++ : i--) { switch (letter = str.charAt(i)) { case '\\': i++; diff --git a/lib/nodes.js b/lib/nodes.js index 7b2ccd5e..2a58c998 100644 --- a/lib/nodes.js +++ b/lib/nodes.js @@ -782,7 +782,7 @@ } }; Range.prototype.compileNode = function(o) { - var compare, idx, incr, intro, step, stepPart, vars; + var compare, cond, idx, incr, step, vars; this.compileVariables(o); if (!o.index) { return this.compileArray(o); @@ -793,10 +793,9 @@ idx = del(o, 'index'); step = del(o, 'step'); vars = ("" + idx + " = " + this.from) + (this.to !== this.toVar ? ", " + this.to : ''); - intro = "(" + this.fromVar + " <= " + this.toVar + " ? " + idx; - compare = "" + intro + " <" + this.equals + " " + this.toVar + " : " + idx + " >" + this.equals + " " + this.toVar + ")"; - stepPart = step ? step.compile(o) : '1'; - incr = step ? "" + idx + " += " + stepPart : "" + intro + " += " + stepPart + " : " + idx + " -= " + stepPart + ")"; + cond = "" + this.fromVar + " <= " + this.toVar; + compare = "" + cond + " ? " + idx + " <" + this.equals + " " + this.toVar + " : " + idx + " >" + this.equals + " " + this.toVar; + incr = step ? "" + idx + " += " + (step.compile(o)) : "" + cond + " ? " + idx + "++ : " + idx + "--"; return "" + vars + "; " + compare + "; " + incr; }; Range.prototype.compileSimple = function(o) { @@ -812,11 +811,11 @@ } }; Range.prototype.compileArray = function(o) { - var body, clause, i, idt, post, pre, range, result, vars, _i, _ref, _ref2, _results; + var body, cond, i, idt, post, pre, range, result, vars, _i, _ref, _ref2, _results; if (this.fromNum && this.toNum && Math.abs(this.fromNum - this.toNum) <= 20) { range = (function() { _results = []; - for (var _i = _ref = +this.fromNum, _ref2 = +this.toNum; _ref <= _ref2 ? _i <= _ref2 : _i >= _ref2; _ref <= _ref2 ? _i += 1 : _i -= 1){ _results.push(_i); } + for (var _i = _ref = +this.fromNum, _ref2 = +this.toNum; _ref <= _ref2 ? _i <= _ref2 : _i >= _ref2; _ref <= _ref2 ? _i++ : _i--){ _results.push(_i); } return _results; }).apply(this, arguments); if (this.exclusive) { @@ -833,8 +832,8 @@ body = this.compileSimple(o); } else { vars = ("" + i + " = " + this.from) + (this.to !== this.toVar ? ", " + this.to : ''); - clause = "" + this.fromVar + " <= " + this.toVar + " ?"; - body = "var " + vars + "; " + clause + " " + i + " <" + this.equals + " " + this.toVar + " : " + i + " >" + this.equals + " " + this.toVar + "; " + clause + " " + i + " += 1 : " + i + " -= 1"; + cond = "" + this.fromVar + " <= " + this.toVar; + body = "var " + vars + "; " + cond + " ? " + i + " <" + this.equals + " " + this.toVar + " : " + i + " >" + this.equals + " " + this.toVar + "; " + cond + " ? " + i + "++ : " + i + "--"; } post = "{ " + result + ".push(" + i + "); }\n" + idt + "return " + result + ";\n" + o.indent; return "(function() {" + pre + "\n" + idt + "for (" + body + ")" + post + "}).apply(this, arguments)"; @@ -1168,9 +1167,6 @@ value = this.value; objects = this.variable.base.objects; if (!(olen = objects.length)) { - if (top) { - return false; - } code = value.compile(o); if (o.level >= LEVEL_OP) { return "(" + code + ")"; @@ -1245,7 +1241,7 @@ if (!top) { assigns.push(vvar); } - code = (compact(assigns)).join(', '); + code = assigns.join(', '); if (o.level < LEVEL_LIST) { return code; } else { @@ -1662,13 +1658,15 @@ Op.prototype.compileExistence = function(o) { var fst, ref; if (this.first.isComplex()) { - ref = o.scope.freeVariable('ref'); - fst = new Parens(new Assign(new Literal(ref), this.first)); + ref = new Literal(o.scope.freeVariable('ref')); + fst = new Parens(new Assign(ref, this.first)); } else { fst = this.first; - ref = fst.compile(o); + ref = fst; } - return new Existence(fst).compile(o) + (" ? " + ref + " : " + (this.second.compile(o, LEVEL_LIST))); + return new If(new Existence(fst), ref, { + type: 'if' + }).addElse(this.second).compile(o); }; Op.prototype.compileUnary = function(o) { var op, parts; @@ -1719,6 +1717,9 @@ } return _results; }).call(this); + if (tests.length === 0) { + return 'false'; + } tests = tests.join(cnj); if (o.level < LEVEL_OP) { return tests; @@ -1801,7 +1802,7 @@ Existence.prototype.compileNode = function(o) { var code, sym; code = this.expression.compile(o, LEVEL_OP); - code = IDENTIFIER.test(code) && !o.scope.check(code) ? this.negated ? "typeof " + code + " == \"undefined\" || " + code + " === null" : "typeof " + code + " != \"undefined\" && " + code + " !== null" : (sym = this.negated ? '==' : '!=', "" + code + " " + sym + " null"); + code = IDENTIFIER.test(code) && !o.scope.check(code) ? this.negated ? "typeof " + code + " == \"undefined\" || " + code + " == null" : "typeof " + code + " != \"undefined\" && " + code + " != null" : (sym = this.negated ? '==' : '!=', "" + code + " " + sym + " null"); if (o.level <= LEVEL_COND) { return code; } else { diff --git a/lib/parser.js b/lib/parser.js index 2b267e83..789e8caf 100755 --- a/lib/parser.js +++ b/lib/parser.js @@ -5,7 +5,7 @@ yy: {}, symbols_: {"error":2,"Root":3,"Body":4,"Block":5,"TERMINATOR":6,"Line":7,"Expression":8,"Statement":9,"Return":10,"Throw":11,"Comment":12,"STATEMENT":13,"Value":14,"Invocation":15,"Code":16,"Operation":17,"Assign":18,"If":19,"Try":20,"While":21,"For":22,"Switch":23,"Class":24,"INDENT":25,"OUTDENT":26,"Identifier":27,"IDENTIFIER":28,"AlphaNumeric":29,"NUMBER":30,"STRING":31,"Literal":32,"JS":33,"REGEX":34,"BOOL":35,"Assignable":36,"=":37,"AssignObj":38,"ObjAssignable":39,":":40,"ThisProperty":41,"RETURN":42,"HERECOMMENT":43,"PARAM_START":44,"ParamList":45,"PARAM_END":46,"FuncGlyph":47,"->":48,"=>":49,"OptComma":50,",":51,"Param":52,"ParamVar":53,"...":54,"Array":55,"Object":56,"Splat":57,"SimpleAssignable":58,"Accessor":59,"Parenthetical":60,"Range":61,"This":62,".":63,"?.":64,"::":65,"Index":66,"INDEX_START":67,"IndexValue":68,"INDEX_END":69,"INDEX_SOAK":70,"INDEX_PROTO":71,"Slice":72,"{":73,"AssignList":74,"}":75,"CLASS":76,"EXTENDS":77,"OptFuncExist":78,"Arguments":79,"SUPER":80,"FUNC_EXIST":81,"CALL_START":82,"CALL_END":83,"ArgList":84,"THIS":85,"@":86,"[":87,"]":88,"RangeDots":89,"..":90,"Arg":91,"SimpleArgs":92,"TRY":93,"Catch":94,"FINALLY":95,"CATCH":96,"THROW":97,"(":98,")":99,"WhileSource":100,"WHILE":101,"WHEN":102,"UNTIL":103,"Loop":104,"LOOP":105,"ForBody":106,"FOR":107,"ForStart":108,"ForSource":109,"ForVariables":110,"OWN":111,"ForValue":112,"FORIN":113,"FOROF":114,"BY":115,"SWITCH":116,"Whens":117,"ELSE":118,"When":119,"LEADING_WHEN":120,"IfBlock":121,"IF":122,"POST_IF":123,"UNARY":124,"-":125,"+":126,"--":127,"++":128,"?":129,"MATH":130,"SHIFT":131,"COMPARE":132,"LOGIC":133,"RELATION":134,"COMPOUND_ASSIGN":135,"$accept":0,"$end":1}, terminals_: {2:"error",6:"TERMINATOR",13:"STATEMENT",25:"INDENT",26:"OUTDENT",28:"IDENTIFIER",30:"NUMBER",31:"STRING",33:"JS",34:"REGEX",35:"BOOL",37:"=",40:":",42:"RETURN",43:"HERECOMMENT",44:"PARAM_START",46:"PARAM_END",48:"->",49:"=>",51:",",54:"...",63:".",64:"?.",65:"::",67:"INDEX_START",69:"INDEX_END",70:"INDEX_SOAK",71:"INDEX_PROTO",73:"{",75:"}",76:"CLASS",77:"EXTENDS",80:"SUPER",81:"FUNC_EXIST",82:"CALL_START",83:"CALL_END",85:"THIS",86:"@",87:"[",88:"]",90:"..",93:"TRY",95:"FINALLY",96:"CATCH",97:"THROW",98:"(",99:")",101:"WHILE",102:"WHEN",103:"UNTIL",105:"LOOP",107:"FOR",111:"OWN",113:"FORIN",114:"FOROF",115:"BY",116:"SWITCH",118:"ELSE",120:"LEADING_WHEN",122:"IF",123:"POST_IF",124:"UNARY",125:"-",126:"+",127:"--",128:"++",129:"?",130:"MATH",131:"SHIFT",132:"COMPARE",133:"LOGIC",134:"RELATION",135:"COMPOUND_ASSIGN"}, productions_: [0,[3,0],[3,1],[3,2],[4,1],[4,3],[4,2],[7,1],[7,1],[9,1],[9,1],[9,1],[9,1],[8,1],[8,1],[8,1],[8,1],[8,1],[8,1],[8,1],[8,1],[8,1],[8,1],[8,1],[5,2],[5,3],[27,1],[29,1],[29,1],[32,1],[32,1],[32,1],[32,1],[18,3],[18,5],[38,1],[38,3],[38,5],[38,1],[39,1],[39,1],[39,1],[10,2],[10,1],[12,1],[16,5],[16,2],[47,1],[47,1],[50,0],[50,1],[45,0],[45,1],[45,3],[52,1],[52,2],[52,3],[53,1],[53,1],[53,1],[53,1],[57,2],[58,1],[58,2],[58,2],[58,1],[36,1],[36,1],[36,1],[14,1],[14,1],[14,1],[14,1],[14,1],[59,2],[59,2],[59,2],[59,1],[59,1],[66,3],[66,2],[66,2],[68,1],[68,1],[56,4],[74,0],[74,1],[74,3],[74,4],[74,6],[24,1],[24,2],[24,3],[24,4],[24,2],[24,3],[24,4],[24,5],[15,3],[15,3],[15,1],[15,2],[78,0],[78,1],[79,2],[79,4],[62,1],[62,1],[41,2],[55,2],[55,4],[89,1],[89,1],[61,5],[72,3],[72,2],[72,2],[84,1],[84,3],[84,4],[84,4],[84,6],[91,1],[91,1],[92,1],[92,3],[20,2],[20,3],[20,4],[20,5],[94,3],[11,2],[60,3],[60,5],[100,2],[100,4],[100,2],[100,4],[21,2],[21,2],[21,2],[21,1],[104,2],[104,2],[22,2],[22,2],[22,2],[106,2],[106,2],[108,2],[108,3],[112,1],[112,1],[112,1],[110,1],[110,3],[109,2],[109,2],[109,4],[109,4],[109,4],[109,6],[109,6],[23,5],[23,7],[23,4],[23,6],[117,1],[117,2],[119,3],[119,4],[121,3],[121,5],[19,1],[19,3],[19,3],[19,3],[17,2],[17,2],[17,2],[17,2],[17,2],[17,2],[17,2],[17,2],[17,3],[17,3],[17,3],[17,3],[17,3],[17,3],[17,3],[17,3],[17,5],[17,3]], -performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$) { +performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) { var $0 = $$.length - 1; switch (yystate) { @@ -475,6 +475,7 @@ parse: function parse(input) { var self = this, stack = [0], vstack = [null], // semantic value stack + lstack = [], // location stack table = this.table, yytext = '', yylineno = 0, @@ -488,6 +489,10 @@ parse: function parse(input) { this.lexer.setInput(input); this.lexer.yy = this.yy; this.yy.lexer = this.lexer; + if (typeof this.lexer.yylloc == 'undefined') + this.lexer.yylloc = {}; + var yyloc = this.lexer.yylloc; + lstack.push(yyloc); if (typeof this.yy.parseError === 'function') this.parseError = this.yy.parseError; @@ -495,6 +500,7 @@ parse: function parse(input) { function popStack (n) { stack.length = stack.length - 2*n; vstack.length = vstack.length - n; + lstack.length = lstack.length - n; } function lex() { @@ -540,7 +546,7 @@ parse: function parse(input) { ("'"+(this.terminals_[symbol] || symbol)+"'")); } this.parseError(errStr, - {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, expected: expected}); + {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected}); } // just recovered from another error @@ -553,6 +559,7 @@ parse: function parse(input) { yyleng = this.lexer.yyleng; yytext = this.lexer.yytext; yylineno = this.lexer.yylineno; + yyloc = this.lexer.yylloc; symbol = lex(); } @@ -568,7 +575,7 @@ parse: function parse(input) { popStack(1); state = stack[stack.length-1]; } - + preErrorSymbol = symbol; // save the lookahead token symbol = TERROR; // insert generic error symbol as new lookahead state = stack[stack.length-1]; @@ -588,12 +595,14 @@ parse: function parse(input) { stack.push(symbol); vstack.push(this.lexer.yytext); + lstack.push(this.lexer.yylloc); stack.push(action[1]); // push state symbol = null; if (!preErrorSymbol) { // normal execution/no error yyleng = this.lexer.yyleng; yytext = this.lexer.yytext; yylineno = this.lexer.yylineno; + yyloc = this.lexer.yylloc; if (recovering > 0) recovering--; } else { // error just occurred, resume old lookahead f/ before error @@ -609,7 +618,14 @@ parse: function parse(input) { // perform semantic action yyval.$ = vstack[vstack.length-len]; // default to $$ = $1 - r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack); + // default location, uses first token for firsts, last for lasts + yyval._$ = { + first_line: lstack[lstack.length-(len||1)].first_line, + last_line: lstack[lstack.length-1].last_line, + first_column: lstack[lstack.length-(len||1)].first_column, + last_column: lstack[lstack.length-1].last_column + }; + r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack); if (typeof r !== 'undefined') { return r; @@ -619,10 +635,12 @@ parse: function parse(input) { if (len) { stack = stack.slice(0,-1*len*2); vstack = vstack.slice(0, -1*len); + lstack = lstack.slice(0, -1*len); } stack.push(this.productions_[action[1]][0]); // push nonterminal (reduce) vstack.push(yyval.$); + lstack.push(yyval._$); // goto new state = table[STATE][NONTERMINAL] newState = table[stack[stack.length-2]][stack[stack.length-1]]; stack.push(newState); diff --git a/src/coffee-script.coffee b/src/coffee-script.coffee index 83135240..15dae184 100755 --- a/src/coffee-script.coffee +++ b/src/coffee-script.coffee @@ -60,9 +60,15 @@ exports.run = (code, options) -> # Set the filename. root.filename = process.argv[1] = if options.filename then fs.realpathSync(options.filename) else '.' - + # Clear the module cache. root.moduleCache = {} if root.moduleCache + + # Assign paths for node_modules loading + if process.binding('natives').module + {Module} = require 'module' + root.paths = Module._nodeModulePaths path.dirname options.filename + # Compile. if path.extname(root.filename) isnt '.coffee' or require.extensions root._compile compile(code, options), root.filename diff --git a/src/nodes.coffee b/src/nodes.coffee index 99370609..7d7aa6c0 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -642,10 +642,9 @@ exports.Range = class Range extends Base idx = del o, 'index' step = del o, 'step' vars = "#{idx} = #{@from}" + if @to isnt @toVar then ", #{@to}" else '' - intro = "(#{@fromVar} <= #{@toVar} ? #{idx}" - compare = "#{intro} <#{@equals} #{@toVar} : #{idx} >#{@equals} #{@toVar})" - stepPart = if step then step.compile(o) else '1' - incr = if step then "#{idx} += #{stepPart}" else "#{intro} += #{stepPart} : #{idx} -= #{stepPart})" + cond = "#{@fromVar} <= #{@toVar}" + compare = "#{cond} ? #{idx} <#{@equals} #{@toVar} : #{idx} >#{@equals} #{@toVar}" + incr = if step then "#{idx} += #{step.compile(o)}" else "#{cond} ? #{idx}++ : #{idx}--" "#{vars}; #{compare}; #{incr}" # Compile a simple range comprehension, with integers. @@ -671,11 +670,11 @@ exports.Range = class Range extends Base pre = "\n#{idt}#{result} = [];" if @fromNum and @toNum o.index = i - body = @compileSimple o + body = @compileSimple o else - vars = "#{i} = #{@from}" + if @to isnt @toVar then ", #{@to}" else '' - clause = "#{@fromVar} <= #{@toVar} ?" - body = "var #{vars}; #{clause} #{i} <#{@equals} #{@toVar} : #{i} >#{@equals} #{@toVar}; #{clause} #{i} += 1 : #{i} -= 1" + vars = "#{i} = #{@from}" + if @to isnt @toVar then ", #{@to}" else '' + cond = "#{@fromVar} <= #{@toVar}" + body = "var #{vars}; #{cond} ? #{i} <#{@equals} #{@toVar} : #{i} >#{@equals} #{@toVar}; #{cond} ? #{i}++ : #{i}--" post = "{ #{result}.push(#{i}); }\n#{idt}return #{result};\n#{o.indent}" "(function() {#{pre}\n#{idt}for (#{body})#{post}}).apply(this, arguments)" @@ -932,7 +931,6 @@ exports.Assign = class Assign extends Base {value} = this {objects} = @variable.base unless olen = objects.length - return false if top code = value.compile o return if o.level >= LEVEL_OP then "(#{code})" else code isObject = @variable.isObject() @@ -993,7 +991,7 @@ exports.Assign = class Assign extends Base val = new Value new Literal(vvar), [new (if acc then Access else Index) idx] assigns.push new Assign(obj, val, null, param: @param).compile o, LEVEL_TOP assigns.push vvar unless top - code = (compact assigns).join ', ' + code = assigns.join ', ' if o.level < LEVEL_LIST then code else "(#{code})" # When compiling a conditional assignment, take care to ensure that the @@ -1301,12 +1299,12 @@ exports.Op = class Op extends Base compileExistence: (o) -> if @first.isComplex() - ref = o.scope.freeVariable 'ref' - fst = new Parens new Assign new Literal(ref), @first + ref = new Literal o.scope.freeVariable 'ref' + fst = new Parens new Assign ref, @first else fst = @first - ref = fst.compile o - new Existence(fst).compile(o) + " ? #{ref} : #{ @second.compile o, LEVEL_LIST }" + ref = fst + new If(new Existence(fst), ref, type: 'if').addElse(@second).compile o # Compile a unary **Op**. compileUnary: (o) -> @@ -1340,6 +1338,7 @@ exports.In = class In extends Base [cmp, cnj] = if @negated then [' !== ', ' && '] else [' === ', ' || '] tests = for item, i in @array.base.objects (if i then ref else sub) + cmp + item.compile o, LEVEL_OP + return 'false' if tests.length is 0 tests = tests.join cnj if o.level < LEVEL_OP then tests else "(#{tests})" @@ -1419,9 +1418,9 @@ exports.Existence = class Existence extends Base code = @expression.compile o, LEVEL_OP code = if IDENTIFIER.test(code) and not o.scope.check code if @negated - "typeof #{code} == \"undefined\" || #{code} === null" + "typeof #{code} == \"undefined\" || #{code} == null" else - "typeof #{code} != \"undefined\" && #{code} !== null" + "typeof #{code} != \"undefined\" && #{code} != null" else sym = if @negated then '==' else '!=' "#{code} #{sym} null" diff --git a/test/array_literals.coffee b/test/array_literals.coffee index dd088d3a..bc4f9fa6 100644 --- a/test/array_literals.coffee +++ b/test/array_literals.coffee @@ -70,3 +70,8 @@ test "array splats with nested arrays", -> list = [1, 2, a...] arrayEq list, [1, 2, [nonce]] +test "#1274: `[] = a()` compiles to `false` instead of `a()`", -> + a = false + fn = -> a = true + [] = fn() + ok a diff --git a/test/assignment.coffee b/test/assignment.coffee index 077ae6f7..f9fbb5e0 100644 --- a/test/assignment.coffee +++ b/test/assignment.coffee @@ -229,6 +229,10 @@ test "destructuring assignment against an expression", -> eq a, y eq b, z +test "bracket insertion when necessary", -> + [a] = [0] ? [1] + eq a, 0 + # for implicit destructuring assignment in comprehensions, see the comprehension tests test "destructuring assignment with context (@) properties", -> diff --git a/test/object_literals.coffee b/test/object_literals.coffee index 1b164d75..d39f3648 100644 --- a/test/object_literals.coffee +++ b/test/object_literals.coffee @@ -210,3 +210,8 @@ test "some weird indentation in YAML-style object literals", -> f: 1 eq 1, obj[1] +test "#1274: `{} = a()` compiles to `false` instead of `a()`", -> + a = false + fn = -> a = true + {} = fn() + ok a diff --git a/test/operators.coffee b/test/operators.coffee index 97ea24eb..87a0a6f5 100644 --- a/test/operators.coffee +++ b/test/operators.coffee @@ -184,6 +184,10 @@ test "#768: `in` should preserve evaluation order", -> ok a() not in [b(),c()] eq 3, share +test "#1099: empty array after `in` should compile to `false`", -> + eq 1, [5 in []].length + eq false, do -> return 0 in [] + # Chained Comparison