<commit_before>Liquid = require "../liquid"
_ = require("underscore")._
futures = require "futures"

module.exports = class Context

  constructor: (environments = {}, outerScope = {}, registers = {}, rethrowErrors = false) ->
    @environments = _.flatten [environments]
    @scopes = [outerScope or {}]
    @registers = registers
    @errors = []
    @rethrowErrors = rethrowErrors
    @strainer = Liquid.Strainer.create(@)
    @squashInstanceAssignsWithEnvironments()


  # Adds filters to this context.
  #
  # Note that this does not register the filters with the main
  # Template object. see <tt>Template.register_filter</tt>
  # for that
  addFilters: (filters) ->
    filters = _([filters]).chain().flatten().compact().value()
    filters.forEach (filter) =>
      unless filter instanceof Object
        throw new Error("Expected Object but got: #{typeof(filter)}")

      _.extend @strainer, filter

  handleError: (e) ->
    @errors.push e
    throw e if @rethrowErrors

    if e instanceof Liquid.SyntaxError
      "Liquid syntax error: #{e.message}"
    else
      "Liquid error: #{e.message}"

  invoke: (method, args...) ->
    if @strainer[method]?
      f = @strainer[method]
      f.apply(@strainer, args)
    else
      args?[0]

  push: (newScope = {}) ->
    @scopes.unshift newScope
    throw new Error("Nesting too deep") if @scopes.length > 100

  merge: (newScope = {}) ->
    _(@scopes[0]).extend(newScope)

  pop: ->
    throw new Error("ContextError") if @scopes.length <= 1
    @scopes.shift()

  lastScope: ->
    @scopes[@scopes.length-1]

  # Pushes a new local scope on the stack, pops it at the end of the block
  #
  # Example:
  #   context.stack do
  #      context['var'] = 'hi'
  #   end
  #
  #   context['var]  #=> nil
  stack: (newScope = {}, f) ->
    popLater = false

    try
      if arguments.length < 2
        f = newScope
        newScope = {}

      @push(newScope)
      result = f()

      if f?.isFuture?
        f.when => @pop()
        popLater = true

      result
    finally
      @pop() unless popLater

  clearInstanceAssigns: ->
    @scopes[0] = {}

  # Only allow String, Numeric, Hash, Array, Proc, Boolean
  # or <tt>Liquid::Drop</tt>
  set: (key, value) ->
    @scopes[0][key] = value

  get: (key) ->
    @resolve(key)

  hasKey: (key) ->
    !!@resolve(key)

  # PRIVATE API

  @Literals =
    'null': null
    'nil': null
    '': null
    'true': true
    'false': false
    'empty': (v) -> !v or v.length == 0
    'blank': (v) -> !v or v.length == 0

  # Look up variable, either resolve directly after considering the name.
  # We can directly handle Strings, digits, floats and booleans (true,false).
  # If no match is made we lookup the variable in the current scope and
  # later move up to the parent blocks to see if we can resolve
  # the variable somewhere up the tree.
  # Some special keywords return symbols. Those symbols are to be called on the rhs object in expressions
  #
  # Example:
  #   products == empty #=> products.empty?
  resolve: (key) ->
    if _(Liquid.Context.Literals).keys().indexOf(key) >= 0
      Liquid.Context.Literals[key]
    else
      if match = /^'(.*)'$/.exec(key) # Single quoted strings
        match[1]
      else if match = /^"(.*)"$/.exec(key) # Double quoted strings
        match[1]
      else if match = /^(\d+)$/.exec(key) # Integer and floats
        Number(match[1])
      else if match = /^\((\S+)\.\.(\S+)\)$/.exec(key) # Ranges
        lo = Number(resolve(match[1]))
        hi = Number(resolve(match[2]))
        # TODO: generate Range
      else if match = /^(\d[\d\.]+)$/.exec(key) # Floats
        Number(match[1])
      else
        @variable(key)


  findVariable: (key) ->
    scope = _(@scopes).detect (s) ->
      s.hasOwnProperty?(key)

    variable = null

    scope or= _(@environments).detect (e) =>
      variable = @lookupAndEvaluate(e, key)

    scope or= @environments[@environments.length-1] or @scopes[@scopes.length-1]

    variable or= @lookupAndEvaluate(scope, key)

    liquify(variable)

  variable: (markup) ->
    future = futures.future()
    unfuture = Liquid.Helpers.unfuture

    parts = Liquid.Helpers.scan(markup, Liquid.VariableParser)
    squareBracketed = /^\[(.*)\]$/

    firstPart = parts.shift()<commit_msg>fixed a bug where context.stack would pop the scope too early<commit_after>Liquid = require "../liquid"
_ = require("underscore")._
futures = require "futures"

module.exports = class Context

  constructor: (environments = {}, outerScope = {}, registers = {}, rethrowErrors = false) ->
    @environments = _.flatten [environments]
    @scopes = [outerScope or {}]
    @registers = registers
    @errors = []
    @rethrowErrors = rethrowErrors
    @strainer = Liquid.Strainer.create(@)
    @squashInstanceAssignsWithEnvironments()


  # Adds filters to this context.
  #
  # Note that this does not register the filters with the main
  # Template object. see <tt>Template.register_filter</tt>
  # for that
  addFilters: (filters) ->
    filters = _([filters]).chain().flatten().compact().value()
    filters.forEach (filter) =>
      unless filter instanceof Object
        throw new Error("Expected Object but got: #{typeof(filter)}")

      _.extend @strainer, filter

  handleError: (e) ->
    @errors.push e
    throw e if @rethrowErrors

    if e instanceof Liquid.SyntaxError
      "Liquid syntax error: #{e.message}"
    else
      "Liquid error: #{e.message}"

  invoke: (method, args...) ->
    if @strainer[method]?
      f = @strainer[method]
      f.apply(@strainer, args)
    else
      args?[0]

  push: (newScope = {}) ->
    Liquid.log "SCOPE PUSH"
    @scopes.unshift newScope
    throw new Error("Nesting too deep") if @scopes.length > 100

  merge: (newScope = {}) ->
    _(@scopes[0]).extend(newScope)

  pop: ->
    Liquid.log "SCOPE POP"
    throw new Error("ContextError") if @scopes.length <= 1
    @scopes.shift()

  lastScope: ->
    @scopes[@scopes.length-1]

  # Pushes a new local scope on the stack, pops it at the end of the block
  #
  # Example:
  #   context.stack do
  #      context['var'] = 'hi'
  #   end
  #
  #   context['var]  #=> nil
  stack: (newScope = {}, f) ->
    popLater = false

    try
      if arguments.length < 2
        f = newScope
        newScope = {}

      @push(newScope)
      result = f()

      if futures.future.isFuture(result)
        popLater = true
        result.when => @pop()

      result
    finally
      @pop() unless popLater

  clearInstanceAssigns: ->
    @scopes[0] = {}

  # Only allow String, Numeric, Hash, Array, Proc, Boolean
  # or <tt>Liquid::Drop</tt>
  set: (key, value) ->
    Liquid.log "[SET] %s %j", key, value
    @scopes[0][key] = value

  get: (key) ->
    value = @resolve(key)
    Liquid.log "[GET] %s %j", key, value
    value

  hasKey: (key) ->
    !!@resolve(key)

  # PRIVATE API

  @Literals =
    'null': null
    'nil': null
    '': null
    'true': true
    'false': false
    'empty': (v) -> !v or v.length == 0
    'blank': (v) -> !v or v.length == 0

  # Look up variable, either resolve directly after considering the name.
  # We can directly handle Strings, digits, floats and booleans (true,false).
  # If no match is made we lookup the variable in the current scope and
  # later move up to the parent blocks to see if we can resolve
  # the variable somewhere up the tree.
  # Some special keywords return symbols. Those symbols are to be called on the rhs object in expressions
  #
  # Example:
  #   products == empty #=> products.empty?
  resolve: (key) ->
    if _(Liquid.Context.Literals).keys().indexOf(key) >= 0
      Liquid.Context.Literals[key]
    else
      if match = /^'(.*)'$/.exec(key) # Single quoted strings
        match[1]
      else if match = /^"(.*)"$/.exec(key) # Double quoted strings
        match[1]
      else if match = /^(\d+)$/.exec(key) # Integer and floats
        Number(match[1])
      else if match = /^\((\S+)\.\.(\S+)\)$/.exec(key) # Ranges
        lo = Number(resolve(match[1]))
        hi = Number(resolve(match[2]))
        # TODO: generate Range
      else if match = /^(\d[\d\.]+)$/.exec(key) # Floats
        Number(match[1])
      else
        @variable(key)


  findVariable: (key) ->
    scope = _(@scopes).detect (s) ->
      s.hasOwnProperty?(key)

    variable = null

    scope or= _(@environments).detect (e) =>
      variable = @lookupAndEvaluate(e, key)

    scope or= @environments[@environments.length-1] or @scopes[@scopes.length-1]

    variable or= @lookupAndEvaluate(scope, key)

    f = futures.future()
    delivered = false
    result = null

    Liquid.Helpers.unfuture variable, (err, variable) ->
      result = liquify(variable)
      f.deliver(err, result)
      delivered = true

    if delivered then result else f

  variable: (markup) ->
    future = futures.future()
    unfuture = Liquid.Helpers.unfuture

    parts = Liquid.Helpers.scan(markup, Liquid.VariableParser)
    squareBracketed = /^\[(.*)\]$/

    firstPart = parts.shift()
{'fraction_non_alphanumeric': 0.14550911516229437, 'fraction_numerical': 0.004668741662961316, 'mean_word_length': 2.613253012048193, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 64, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3712048', 'n_tokens_mistral': 3018, 'n_tokens_neox': 2883, 'n_words': 822}