• ジャンプ先 ... +
    browser.coffee cake.coffee coffee-script.coffee command.coffee grammar.coffee helpers.coffee index.coffee lexer.coffee nodes.coffee optparse.coffee register.coffee repl.coffee rewriter.coffee scope.litcoffee sourcemap.litcoffee
  • repl.coffee

  • ¶
    fs = require 'fs'
    path = require 'path'
    vm = require 'vm'
    nodeREPL = require 'repl'
    CoffeeScript = require './coffee-script'
    {merge, updateSyntaxError} = require './helpers'
    
    replDefaults =
      prompt: 'coffee> ',
      historyFile: path.join process.env.HOME, '.coffee_history' if process.env.HOME
      historyMaxInputSize: 10240
      eval: (input, context, filename, cb) ->
  • ¶

    XXX: マルチラインハック

        input = input.replace /\uFF00/g, '\n'
  • ¶

    NodeのREPLは改行で終わって括弧で括られた入力を送信します。それをすべてアンラップ(展開)します。

        input = input.replace /^\(([\s\S]*)\n\)$/m, '$1'
  • ¶

    NodeのREPL v6.9.1+はtry/catchステートメントにラップされた入力を送信します。これもアンラップします。

        input = input.replace /^\s*try\s*{([\s\S]*)}\s*catch.*$/m, '$1'
  • ¶

    AST(抽象構文木)ノードが必要で、AST操作を実行します。

        {Block, Assign, Value, Literal} = require './nodes'
    
        try
  • ¶

    クリーンされた入力をトークン化します。

          tokens = CoffeeScript.tokens input
  • ¶

    CoffeeScript.compileのように、参照されている変数名を収集します。

          referencedVars = (
            token[1] for token in tokens when token[0] is 'IDENTIFIER'
          )
  • ¶

    トークンのASTを生成します。

          ast = CoffeeScript.nodes tokens
  • ¶

    _変数に代入を追加して、入力を式にします。

          ast = new Block [
            new Assign (new Value new Literal '__'), ast, '='
          ]
          js = ast.compile {bare: yes, locals: Object.keys(context), referencedVars}
          cb null, runInContext js, context, filename
        catch err
  • ¶

    ASTのcompileはシンタックスエラーにソースコード情報を追加しません。

          updateSyntaxError err, input
          cb err
    
    runInContext = (js, context, filename) ->
      if context is global
        vm.runInThisContext js, filename
      else
        vm.runInContext js, context, filename
    
    addMultilineHandler = (repl) ->
      {rli, inputStream, outputStream} = repl
  • ¶

    Node 0.11.12はAPIを変更して、プロンプトは_promptになりました。

      origPrompt = repl._prompt ? repl.prompt
    
      multiline =
        enabled: off
        initialPrompt: origPrompt.replace /^[^> ]*/, (x) -> x.replace /./g, '-'
        prompt: origPrompt.replace /^[^> ]*>?/, (x) -> x.replace /./g, '.'
        buffer: ''
  • ¶

    Nodeのラインリスナーをプロキシします。

      nodeLineListener = rli.listeners('line')[0]
      rli.removeListener 'line', nodeLineListener
      rli.on 'line', (cmd) ->
        if multiline.enabled
          multiline.buffer += "#{cmd}\n"
          rli.setPrompt multiline.prompt
          rli.prompt true
        else
          rli.setPrompt origPrompt
          nodeLineListener cmd
        return
  • ¶

    Ctrl-vを処理します。

      inputStream.on 'keypress', (char, key) ->
        return unless key and key.ctrl and not key.meta and not key.shift and key.name is 'v'
        if multiline.enabled
  • ¶

    複数の行が入力されるまでいつでもモード間のスイッチを自由に許可します。

          unless multiline.buffer.match /\n/
            multiline.enabled = not multiline.enabled
            rli.setPrompt origPrompt
            rli.prompt true
            return
  • ¶

    現在の行が空でない限りオペレーションを実行しません。

          return if rli.line? and not rli.line.match /^\s*$/
  • ¶

    eval、print、loop

          multiline.enabled = not multiline.enabled
          rli.line = ''
          rli.cursor = 0
          rli.output.cursorTo 0
          rli.output.clearLine 1
  • ¶

    XXX: マルチラインハック

          multiline.buffer = multiline.buffer.replace /\n/g, '\uFF00'
          rli.emit 'line', multiline.buffer
          multiline.buffer = ''
        else
          multiline.enabled = not multiline.enabled
          rli.setPrompt multiline.initialPrompt
          rli.prompt true
        return
  • ¶

    コマンド履歴をファイルに保存してロードします。

    addHistory = (repl, filename, maxSize) ->
      lastLine = null
      try
  • ¶

    ファイル情報とコマンド履歴の最大サイズを取得します。

        stat = fs.statSync filename
        size = Math.min maxSize, stat.size
  • ¶

    ファイルから最後のサイズバイトを読み取ります。

        readFd = fs.openSync filename, 'r'
        buffer = new Buffer(size)
        fs.readSync readFd, buffer, 0, size, stat.size - size
        fs.closeSync readFd
  • ¶

    インタープリターに履歴を設定します。

        repl.rli.history = buffer.toString().split('\n').reverse()
  • ¶

    履歴ファイルが切り捨てられた場合、部分的な行をポップオフする必要があります。

        repl.rli.history.pop() if stat.size > maxSize
  • ¶

    最後の空白の改行をシフトオフします。

        repl.rli.history.shift() if repl.rli.history[0] is ''
        repl.rli.historyIndex = -1
        lastLine = repl.rli.history[0]
    
      fd = fs.openSync filename, 'a'
    
      repl.rli.addListener 'line', (code) ->
        if code and code.length and code isnt '.history' and code isnt '.exit' and lastLine isnt code
  • ¶

    最新のコマンドをファイルに保存します。

          fs.writeSync fd, "#{code}\n"
          lastLine = code
    
      repl.on 'exit', -> fs.closeSync fd
  • ¶

    履歴スタックを表示するコマンドを追加します。

      repl.commands[getCommandId(repl, 'history')] =
        help: 'Show command history'
        action: ->
          repl.outputStream.write "#{repl.rli.history[..].reverse().join '\n'}\n"
          repl.displayPrompt()
    
    getCommandId = (repl, commandName) ->
  • ¶

    Node 0.11はAPIを変更して、「.help」などのコマンドは「help」として保存されます。

      commandsHaveLeadingDot = repl.commands['.help']?
      if commandsHaveLeadingDot then ".#{commandName}" else commandName
    
    module.exports =
      start: (opts = {}) ->
        [major, minor, build] = process.versions.node.split('.').map (n) -> parseInt(n, 10)
    
        if major is 0 and minor < 8
          console.warn "Node 0.8.0+ required for CoffeeScript REPL"
          process.exit 1
    
        CoffeeScript.register()
        process.argv = ['coffee'].concat process.argv[2..]
        opts = merge replDefaults, opts
        repl = nodeREPL.start opts
        runInContext opts.prelude, repl.context, 'prelude' if opts.prelude
        repl.on 'exit', -> repl.outputStream.write '\n' if not repl.rli.closed
        addMultilineHandler repl
        addHistory repl, opts.historyFile, opts.historyMaxInputSize if opts.historyFile
  • ¶

    Node REPLから継承されたヘルプを調整します。

        repl.commands[getCommandId(repl, 'load')].help = 'Load code from a file into this REPL session'
        repl