environment.rb 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. require 'set'
  2. module Sass
  3. # The lexical environment for SassScript.
  4. # This keeps track of variable, mixin, and function definitions.
  5. #
  6. # A new environment is created for each level of Sass nesting.
  7. # This allows variables to be lexically scoped.
  8. # The new environment refers to the environment in the upper scope,
  9. # so it has access to variables defined in enclosing scopes,
  10. # but new variables are defined locally.
  11. #
  12. # Environment also keeps track of the {Engine} options
  13. # so that they can be made available to {Sass::Script::Functions}.
  14. class Environment
  15. # The enclosing environment,
  16. # or nil if this is the global environment.
  17. #
  18. # @return [Environment]
  19. attr_reader :parent
  20. attr_writer :options
  21. # @param parent [Environment] See \{#parent}
  22. def initialize(parent = nil)
  23. @parent = parent
  24. unless parent
  25. @stack = []
  26. @mixins_in_use = Set.new
  27. @files_in_use = Set.new
  28. end
  29. end
  30. # The options hash.
  31. # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
  32. #
  33. # @return [{Symbol => Object}]
  34. def options
  35. @options || parent_options || {}
  36. end
  37. # Push a new stack frame onto the mixin/include stack.
  38. #
  39. # @param frame_info [{Symbol => Object}]
  40. # Frame information has the following keys:
  41. #
  42. # `:filename`
  43. # : The name of the file in which the lexical scope changed.
  44. #
  45. # `:mixin`
  46. # : The name of the mixin in which the lexical scope changed,
  47. # or `nil` if it wasn't within in a mixin.
  48. #
  49. # `:line`
  50. # : The line of the file on which the lexical scope changed. Never nil.
  51. def push_frame(frame_info)
  52. top_of_stack = stack.last
  53. if top_of_stack && top_of_stack.delete(:prepared)
  54. top_of_stack.merge!(frame_info)
  55. else
  56. stack.push(top_of_stack = frame_info)
  57. end
  58. mixins_in_use << top_of_stack[:mixin] if top_of_stack[:mixin]
  59. files_in_use << top_of_stack[:filename] if top_of_stack[:filename]
  60. end
  61. # Like \{#push\_frame}, but next time a stack frame is pushed,
  62. # it will be merged with this frame.
  63. #
  64. # @param frame_info [{Symbol => Object}] Same as for \{#push\_frame}.
  65. def prepare_frame(frame_info)
  66. push_frame(frame_info.merge(:prepared => true))
  67. end
  68. # Pop a stack frame from the mixin/include stack.
  69. def pop_frame
  70. pop_and_unuse if stack.last && stack.last[:prepared]
  71. pop_and_unuse
  72. end
  73. # A list of stack frames in the mixin/include stack.
  74. # The last element in the list is the most deeply-nested frame.
  75. #
  76. # @return [Array<{Symbol => Object}>] The stack frames,
  77. # of the form passed to \{#push\_frame}.
  78. def stack
  79. @stack ||= @parent.stack
  80. end
  81. # A set of names of mixins currently present in the stack.
  82. #
  83. # @return [Set<String>] The mixin names.
  84. def mixins_in_use
  85. @mixins_in_use ||= @parent.mixins_in_use
  86. end
  87. # A set of names of files currently present in the stack.
  88. #
  89. # @return [Set<String>] The filenames.
  90. def files_in_use
  91. @files_in_use ||= @parent.files_in_use
  92. end
  93. def stack_trace
  94. trace = []
  95. stack.reverse.each_with_index do |entry, i|
  96. msg = "#{i == 0 ? "on" : "from"} line #{entry[:line]}"
  97. msg << " of #{entry[:filename] || "an unknown file"}"
  98. msg << ", in `#{entry[:mixin]}'" if entry[:mixin]
  99. trace << msg
  100. end
  101. trace
  102. end
  103. private
  104. def pop_and_unuse
  105. popped = stack.pop
  106. mixins_in_use.delete(popped[:mixin]) if popped && popped[:mixin]
  107. files_in_use.delete(popped[:filename]) if popped && popped[:filename]
  108. popped
  109. end
  110. def parent_options
  111. @parent_options ||= @parent && @parent.options
  112. end
  113. class << self
  114. private
  115. UNDERSCORE, DASH = '_', '-'
  116. # Note: when updating this,
  117. # update sass/yard/inherited_hash.rb as well.
  118. def inherited_hash(name)
  119. class_eval <<RUBY, __FILE__, __LINE__ + 1
  120. def #{name}(name)
  121. _#{name}(name.tr(UNDERSCORE, DASH))
  122. end
  123. def _#{name}(name)
  124. (@#{name}s && @#{name}s[name]) || @parent && @parent._#{name}(name)
  125. end
  126. protected :_#{name}
  127. def set_#{name}(name, value)
  128. name = name.tr(UNDERSCORE, DASH)
  129. @#{name}s[name] = value unless try_set_#{name}(name, value)
  130. end
  131. def try_set_#{name}(name, value)
  132. @#{name}s ||= {}
  133. if @#{name}s.include?(name)
  134. @#{name}s[name] = value
  135. true
  136. elsif @parent
  137. @parent.try_set_#{name}(name, value)
  138. else
  139. false
  140. end
  141. end
  142. protected :try_set_#{name}
  143. def set_local_#{name}(name, value)
  144. @#{name}s ||= {}
  145. @#{name}s[name.tr(UNDERSCORE, DASH)] = value
  146. end
  147. RUBY
  148. end
  149. end
  150. # variable
  151. # Script::Literal
  152. inherited_hash :var
  153. # mixin
  154. # Sass::Callable
  155. inherited_hash :mixin
  156. # function
  157. # Sass::Callable
  158. inherited_hash :function
  159. end
  160. end