141 lines
4.1 KiB
Ruby
141 lines
4.1 KiB
Ruby
|
module Sass
|
||
|
# A class representing the stack when compiling a Sass file.
|
||
|
class Stack
|
||
|
# TODO: use this to generate stack information for Sass::SyntaxErrors.
|
||
|
|
||
|
# A single stack frame.
|
||
|
class Frame
|
||
|
# The filename of the file in which this stack frame was created.
|
||
|
#
|
||
|
# @return [String]
|
||
|
attr_reader :filename
|
||
|
|
||
|
# The line number on which this stack frame was created.
|
||
|
#
|
||
|
# @return [String]
|
||
|
attr_reader :line
|
||
|
|
||
|
# The type of this stack frame. This can be `:import`, `:mixin`, or
|
||
|
# `:base`.
|
||
|
#
|
||
|
# `:base` indicates that this is the bottom-most frame, meaning that it
|
||
|
# represents a single line of code rather than a nested context. The stack
|
||
|
# will only ever have one base frame, and it will always be the most
|
||
|
# deeply-nested frame.
|
||
|
#
|
||
|
# @return [Symbol?]
|
||
|
attr_reader :type
|
||
|
|
||
|
# The name of the stack frame. For mixin frames, this is the mixin name;
|
||
|
# otherwise, it's `nil`.
|
||
|
#
|
||
|
# @return [String?]
|
||
|
attr_reader :name
|
||
|
|
||
|
def initialize(filename, line, type, name = nil)
|
||
|
@filename = filename
|
||
|
@line = line
|
||
|
@type = type
|
||
|
@name = name
|
||
|
end
|
||
|
|
||
|
# Whether this frame represents an import.
|
||
|
#
|
||
|
# @return [Boolean]
|
||
|
def is_import?
|
||
|
type == :import
|
||
|
end
|
||
|
|
||
|
# Whether this frame represents a mixin.
|
||
|
#
|
||
|
# @return [Boolean]
|
||
|
def is_mixin?
|
||
|
type == :mixin
|
||
|
end
|
||
|
|
||
|
# Whether this is the base frame.
|
||
|
#
|
||
|
# @return [Boolean]
|
||
|
def is_base?
|
||
|
type == :base
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# The stack frames. The last frame is the most deeply-nested.
|
||
|
#
|
||
|
# @return [Array<Frame>]
|
||
|
attr_reader :frames
|
||
|
|
||
|
def initialize
|
||
|
@frames = []
|
||
|
end
|
||
|
|
||
|
# Pushes a base frame onto the stack.
|
||
|
#
|
||
|
# @param filename [String] See \{Frame#filename}.
|
||
|
# @param line [String] See \{Frame#line}.
|
||
|
# @yield [] A block in which the new frame is on the stack.
|
||
|
def with_base(filename, line)
|
||
|
with_frame(filename, line, :base) {yield}
|
||
|
end
|
||
|
|
||
|
# Pushes an import frame onto the stack.
|
||
|
#
|
||
|
# @param filename [String] See \{Frame#filename}.
|
||
|
# @param line [String] See \{Frame#line}.
|
||
|
# @yield [] A block in which the new frame is on the stack.
|
||
|
def with_import(filename, line)
|
||
|
with_frame(filename, line, :import) {yield}
|
||
|
end
|
||
|
|
||
|
# Pushes a mixin frame onto the stack.
|
||
|
#
|
||
|
# @param filename [String] See \{Frame#filename}.
|
||
|
# @param line [String] See \{Frame#line}.
|
||
|
# @param name [String] See \{Frame#name}.
|
||
|
# @yield [] A block in which the new frame is on the stack.
|
||
|
def with_mixin(filename, line, name)
|
||
|
with_frame(filename, line, :mixin, name) {yield}
|
||
|
end
|
||
|
|
||
|
# Pushes a function frame onto the stack.
|
||
|
#
|
||
|
# @param filename [String] See \{Frame#filename}.
|
||
|
# @param line [String] See \{Frame#line}.
|
||
|
# @param name [String] See \{Frame#name}.
|
||
|
# @yield [] A block in which the new frame is on the stack.
|
||
|
def with_function(filename, line, name)
|
||
|
with_frame(filename, line, :function, name) {yield}
|
||
|
end
|
||
|
|
||
|
# Pushes a function frame onto the stack.
|
||
|
#
|
||
|
# @param filename [String] See \{Frame#filename}.
|
||
|
# @param line [String] See \{Frame#line}.
|
||
|
# @param name [String] See \{Frame#name}.
|
||
|
# @yield [] A block in which the new frame is on the stack.
|
||
|
def with_directive(filename, line, name)
|
||
|
with_frame(filename, line, :directive, name) {yield}
|
||
|
end
|
||
|
|
||
|
def to_s
|
||
|
(frames.reverse + [nil]).each_cons(2).each_with_index.
|
||
|
map do |(frame, caller), i|
|
||
|
"#{i == 0 ? 'on' : 'from'} line #{frame.line}" +
|
||
|
" of #{frame.filename || 'an unknown file'}" +
|
||
|
(caller && caller.name ? ", in `#{caller.name}'" : "")
|
||
|
end.join("\n")
|
||
|
end
|
||
|
|
||
|
private
|
||
|
|
||
|
def with_frame(filename, line, type, name = nil)
|
||
|
@frames.pop if @frames.last && @frames.last.type == :base
|
||
|
@frames.push(Frame.new(filename, line, type, name))
|
||
|
yield
|
||
|
ensure
|
||
|
@frames.pop unless type == :base && @frames.last && @frames.last.type != :base
|
||
|
end
|
||
|
end
|
||
|
end
|