diff --git a/src/grammar.coffee b/src/grammar.coffee index d28127bf21..ad8c1cf0b1 100644 --- a/src/grammar.coffee +++ b/src/grammar.coffee @@ -97,6 +97,8 @@ grammar = o 'Return' o 'Comment' o 'STATEMENT', -> new StatementLiteral $1 + o 'Import' + o 'Export' ] # All the different types of expressions in our language. The basic unit of @@ -117,8 +119,6 @@ grammar = o 'Class' o 'Throw' o 'Yield' - o 'Import' - o 'Export' ] Yield: [ @@ -351,8 +351,33 @@ grammar = o 'CLASS SimpleAssignable EXTENDS Expression Block', -> new Class $2, $4, $5 ] + NamedImports: [ + o '{ }', -> new ImportsList [] + o '{ ImportsList OptComma }', -> new ImportsList $2 + ] + + ImportClause: [ + o 'NamedImports' + o 'ImportSpecifier' + o '* IMPORT_AS Identifier' + ] + + ImportSpecifier: [ + o 'Identifier' + o 'Identifier IMPORT_AS Identifier', -> new ImportSpecifier $1, $3 + ] + + ImportsList: [ + o 'ImportSpecifier', -> [$1] + o 'ImportsList , ImportSpecifier', -> $1.concat $3 + o 'ImportsList OptComma TERMINATOR ImportSpecifier', -> $1.concat $4 + o 'INDENT ImportsList OptComma OUTDENT', -> $2 + o 'ImportsList OptComma INDENT ImportsList OptComma OUTDENT', -> $1.concat $4 + ] + Import: [ - o 'IMPORT Expression', -> new Import $2 + o 'IMPORT String', -> new Import $2 + o 'IMPORT ImportClause IMPORT_FROM String', -> new Import $4, $2 ] # Ordinary function invocation, or a chained series of calls. diff --git a/src/lexer.coffee b/src/lexer.coffee index 2a83f8ffe2..52fd2917f0 100644 --- a/src/lexer.coffee +++ b/src/lexer.coffee @@ -43,6 +43,7 @@ exports.Lexer = class Lexer @ends = [] # The stack for pairing up tokens. @tokens = [] # Stream of parsed tokens in the form `['TYPE', value, location data]`. @seenFor = no # Used to recognize FORIN and FOROF tokens. + @seenImport = no # Used to recognize IMPORT FROM? AS? tokens. @chunkLine = opts.line or 0 # The start line for the current @chunk. @@ -113,7 +114,22 @@ exports.Lexer = class Lexer if id is 'from' and @tag() is 'YIELD' @token 'FROM', id return id.length + + if id is 'import' + @seenImport = yes + @token 'IMPORT', id, 0, idLength + return idLength + + if id is 'from' and @seenImport + @token 'IMPORT_FROM', id + return idLength + + if id is 'as' and @seenImport + @token 'IMPORT_AS', id + return idLength + [..., prev] = @tokens + tag = if colon or prev? and (prev[0] in ['.', '?.', '::', '?::'] or @@ -317,9 +333,13 @@ exports.Lexer = class Lexer lineToken: -> return 0 unless match = MULTI_DENT.exec @chunk indent = match[0] + @seenFor = no + @seenImport = no + size = indent.length - 1 - indent.lastIndexOf '\n' noNewlines = @unfinished() + if size - @indebt is @indent if noNewlines then @suppressNewlines() else @newlineToken 0 return indent.length @@ -774,7 +794,7 @@ JS_KEYWORDS = [ 'return', 'throw', 'break', 'continue', 'debugger', 'yield' 'if', 'else', 'switch', 'for', 'while', 'do', 'try', 'catch', 'finally' 'class', 'extends', 'super' - 'export', 'import', 'default' + 'export', 'import', 'from', 'default' ] # CoffeeScript-only keywords. diff --git a/src/nodes.coffee b/src/nodes.coffee index c050929e22..fbbd7708ec 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1226,9 +1226,9 @@ exports.Class = class Class extends Base #### Import exports.Import = class Import extends Base - constructor: (@expression) -> + constructor: (@expression, @identifier) -> - children: ['expression'] + children: ['expression', 'identifier'] isStatement: YES jumps: NO @@ -1236,8 +1236,57 @@ exports.Import = class Import extends Base makeReturn: THIS compileNode: (o) -> - [].concat @makeCode(@tab + 'import '), @expression.compileToFragments(o), @makeCode(';') + code = [] + + code.push @makeCode(@tab + 'import ') + + if @identifier + if @identifier.value? + code.push @makeCode("#{@identifier.value} from ") + else + code.push fragment for fragment in @identifier.compileNode(o) + code.push @makeCode(' from ') + + if @expression.value? + code.push @makeCode(@expression.value) + + code.push @makeCode(';') + code + +exports.ImportsList = class ImportsList extends Base + constructor: (identifiers) -> + @identifiers = identifiers or [] + + children: ['identifiers'] + + compileNode: (o) -> + return [@makeCode('[]')] unless @identifiers.length + o.indent += TAB + + code = [] + compiledList = (identifier.compileToFragments o, LEVEL_LIST for identifier in @identifiers) + + for fragments, index in compiledList + code.push @makeCode(', ') if index + code.push fragments... + + if fragmentsToText(code).includes('\n') + code.unshift @makeCode("{\n#{o.indent}") + code.push @makeCode("\n#{@tab}}") + else + code.unshift @makeCode("{ ") + code.push @makeCode(" }") + + code + +exports.ImportSpecifier = class ImportSpecifier extends Base + constructor: (@original, @alias) -> + + children: ['from', 'as'] + + compileNode: (o) -> + return [@makeCode("#{@original.value} as #{@alias.value}")] #### Assign diff --git a/test/modules.coffee b/test/modules.coffee index 18509ef372..323b56c4dd 100644 --- a/test/modules.coffee +++ b/test/modules.coffee @@ -21,30 +21,35 @@ test "import module", -> # console.log toJS input eq toJS(input), output -# test "module import test, syntax #1", -> -# input = "import foo from 'lib'" -# output = "import foo from 'lib';" -# eq toJS(input), output +test "module import test, syntax #1", -> + input = "import foo from 'lib'" + output = "import foo from 'lib';" + eq toJS(input), output -# test "module import test, syntax #2", -> -# input = "import { foo } from 'lib'" -# output = "import { foo } from 'lib';" -# eq toJS(input), output +test "module import test, syntax #2", -> + input = "import { foo } from 'lib'" + output = "import { foo } from 'lib';" + eq toJS(input), output -# test "module import test, syntax #3", -> -# input = "import { default as foo } from 'lib'" -# output = "import { default as foo } from 'lib';" -# eq toJS(input), output +test "module import test, syntax #3", -> + input = "import { bar as foo } from 'lib'" + output = "import { bar as foo } from 'lib';" + eq toJS(input), output -# test "module import test, syntax #4", -> -# input = "import { square, diag } from 'lib'" -# output = "import { square, diag } from 'lib';" -# eq toJS(input), output +test "module import test, syntax #3", -> + input = "import { oof, bar as foo } from 'lib'" + output = "import { oof, bar as foo } from 'lib';" + eq toJS(input), output -# test "module import test, syntax #5", -> -# input = "import { foo } from 'lib' # with a comment" -# output = "import { foo } from 'lib' ;" -# eq toJS(input), output +test "module import test, syntax #4", -> + input = "import { square, diag } from 'lib'" + output = "import { square, diag } from 'lib';" + eq toJS(input), output + +test "module import test, syntax #5", -> + input = "import { foo } from 'lib' # with a comment" + output = "import { foo } from 'lib';" + eq toJS(input), output # test "module export test, syntax #1", -> # input = "export default mixin"