1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
|
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
|