From d4a180c413db7090e70527b00af348608e61b24d Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 18 Feb 2010 20:09:41 -0500 Subject: [PATCH] carefully categorizing JS keywords from JS reserved words from Coffee keywords, so that we can throw syntax errors at compile time if JS keywords are getting assigned to. --- lib/lexer.js | 18 +++++++++++++++--- src/lexer.coffee | 33 ++++++++++++++++++++++++--------- 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/lib/lexer.js b/lib/lexer.js index 2262216f..87fb6e00 100644 --- a/lib/lexer.js +++ b/lib/lexer.js @@ -1,5 +1,5 @@ (function(){ - var ACCESSORS, ASSIGNMENT, BEFORE_WHEN, CALLABLE, CODE, COMMENT, COMMENT_CLEANER, HEREDOC, HEREDOC_INDENT, IDENTIFIER, JS, JS_CLEANER, KEYWORDS, LAST_DENT, LAST_DENTS, MULTILINER, MULTI_DENT, NOT_REGEX, NO_NEWLINE, NUMBER, OPERATOR, REGEX, RESERVED, Rewriter, STRING, STRING_NEWLINES, WHITESPACE, lex; + var ACCESSORS, ASSIGNMENT, BEFORE_WHEN, CALLABLE, CODE, COFFEE_KEYWORDS, COMMENT, COMMENT_CLEANER, HEREDOC, HEREDOC_INDENT, IDENTIFIER, JS, JS_CLEANER, JS_FORBIDDEN, JS_KEYWORDS, KEYWORDS, LAST_DENT, LAST_DENTS, MULTILINER, MULTI_DENT, NOT_REGEX, NO_NEWLINE, NUMBER, OPERATOR, REGEX, RESERVED, Rewriter, STRING, STRING_NEWLINES, WHITESPACE, lex; if ((typeof process !== "undefined" && process !== null)) { Rewriter = require('./rewriter').Rewriter; } else { @@ -11,11 +11,17 @@ // pushing some extra smarts into the Lexer. exports.Lexer = (lex = function lex() { }); // Constants ============================================================ + // Keywords that CoffeScript shares in common with JS. + JS_KEYWORDS = ["if", "else", "true", "false", "new", "return", "try", "catch", "finally", "throw", "break", "continue", "for", "in", "while", "delete", "instanceof", "typeof", "switch", "super", "extends"]; + // CoffeeScript-only keywords -- which we're more relaxed about allowing. + COFFEE_KEYWORDS = ["then", "unless", "yes", "no", "on", "off", "and", "or", "is", "isnt", "not", "of", "by", "where", "when"]; // The list of keywords passed verbatim to the parser. - KEYWORDS = ["if", "else", "then", "unless", "true", "false", "yes", "no", "on", "off", "and", "or", "is", "isnt", "not", "new", "return", "try", "catch", "finally", "throw", "break", "continue", "for", "in", "of", "by", "where", "while", "delete", "instanceof", "typeof", "switch", "when", "super", "extends"]; + KEYWORDS = JS_KEYWORDS.concat(COFFEE_KEYWORDS); // The list of keywords that are reserved by JavaScript, but not used, and aren't // used by CoffeeScript. Using these will throw an error at compile time. RESERVED = ["case", "default", "do", "function", "var", "void", "with", "class", "const", "let", "debugger", "enum", "export", "import", "native"]; + // JavaScript keywords and reserved words together, excluding CoffeeScript ones. + JS_FORBIDDEN = JS_KEYWORDS.concat(RESERVED); // Token matching regexes. (keep the IDENTIFIER regex in sync with AssignNode.) IDENTIFIER = /^([a-zA-Z$_](\w|\$)*)/; NUMBER = /^(\b((0(x|X)[0-9a-fA-F]+)|([0-9]+(\.[0-9]+)?(e[+\-]?[0-9]+)?)))\b/i; @@ -286,7 +292,13 @@ this.tag_parameters(); } value = value || this.chunk.substr(0, 1); - tag = value.match(ASSIGNMENT) ? 'ASSIGN' : value; + tag = value; + if (value.match(ASSIGNMENT)) { + tag = 'ASSIGN'; + if (JS_FORBIDDEN.indexOf(this.value()) >= 0) { + throw new Error('SyntaxError: Reserved word "' + this.value() + '" on line ' + this.line + ' can\'t be assigned'); + } + } if (value === ';') { tag = 'TERMINATOR'; } diff --git a/src/lexer.coffee b/src/lexer.coffee index ab45c4cb..ef80a8d5 100644 --- a/src/lexer.coffee +++ b/src/lexer.coffee @@ -11,20 +11,29 @@ exports.Lexer: lex: -> # Constants ============================================================ -# The list of keywords passed verbatim to the parser. -KEYWORDS: [ - "if", "else", "then", "unless", - "true", "false", "yes", "no", "on", "off", - "and", "or", "is", "isnt", "not", +# Keywords that CoffeScript shares in common with JS. +JS_KEYWORDS: [ + "if", "else", + "true", "false", "new", "return", "try", "catch", "finally", "throw", "break", "continue", - "for", "in", "of", "by", "where", "while", + "for", "in", "while", "delete", "instanceof", "typeof", - "switch", "when", - "super", "extends" + "switch", "super", "extends" ] +# CoffeeScript-only keywords -- which we're more relaxed about allowing. +COFFEE_KEYWORDS: [ + "then", "unless", + "yes", "no", "on", "off", + "and", "or", "is", "isnt", "not", + "of", "by", "where", "when" +] + +# The list of keywords passed verbatim to the parser. +KEYWORDS: JS_KEYWORDS.concat COFFEE_KEYWORDS + # The list of keywords that are reserved by JavaScript, but not used, and aren't # used by CoffeeScript. Using these will throw an error at compile time. RESERVED: [ @@ -32,6 +41,9 @@ RESERVED: [ "const", "let", "debugger", "enum", "export", "import", "native" ] +# JavaScript keywords and reserved words together, excluding CoffeeScript ones. +JS_FORBIDDEN: JS_KEYWORDS.concat RESERVED + # Token matching regexes. (keep the IDENTIFIER regex in sync with AssignNode.) IDENTIFIER : /^([a-zA-Z$_](\w|\$)*)/ NUMBER : /^(\b((0(x|X)[0-9a-fA-F]+)|([0-9]+(\.[0-9]+)?(e[+\-]?[0-9]+)?)))\b/i @@ -233,7 +245,10 @@ lex::literal_token: -> value: match and match[1] @tag_parameters() if value and value.match(CODE) value ||= @chunk.substr(0, 1) - tag: if value.match(ASSIGNMENT) then 'ASSIGN' else value + tag: value + if value.match(ASSIGNMENT) + tag: 'ASSIGN' + throw new Error('SyntaxError: Reserved word "' + @value() + '" on line ' + @line + ' can\'t be assigned') if JS_FORBIDDEN.indexOf(@value()) >= 0 tag: 'TERMINATOR' if value == ';' prev: @tokens[@tokens.length - 1]