PageRenderTime 47ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/vendor/ruby/1.9.1/gems/sass-3.2.5/lib/sass/script/lexer.rb

https://github.com/KaylaStuebbe/uwsp-virtual-tour-server
Ruby | 350 lines | 252 code | 38 blank | 60 comment | 31 complexity | 26857fe216c894e368cc48f087cecd23 MD5 | raw file
Possible License(s): Apache-2.0, MIT, GPL-2.0, BSD-3-Clause
  1. require 'sass/scss/rx'
  2. module Sass
  3. module Script
  4. # The lexical analyzer for SassScript.
  5. # It takes a raw string and converts it to individual tokens
  6. # that are easier to parse.
  7. class Lexer
  8. include Sass::SCSS::RX
  9. # A struct containing information about an individual token.
  10. #
  11. # `type`: \[`Symbol`\]
  12. # : The type of token.
  13. #
  14. # `value`: \[`Object`\]
  15. # : The Ruby object corresponding to the value of the token.
  16. #
  17. # `line`: \[`Fixnum`\]
  18. # : The line of the source file on which the token appears.
  19. #
  20. # `offset`: \[`Fixnum`\]
  21. # : The number of bytes into the line the SassScript token appeared.
  22. #
  23. # `pos`: \[`Fixnum`\]
  24. # : The scanner position at which the SassScript token appeared.
  25. Token = Struct.new(:type, :value, :line, :offset, :pos)
  26. # The line number of the lexer's current position.
  27. #
  28. # @return [Fixnum]
  29. attr_reader :line
  30. # The number of bytes into the current line
  31. # of the lexer's current position.
  32. #
  33. # @return [Fixnum]
  34. attr_reader :offset
  35. # A hash from operator strings to the corresponding token types.
  36. OPERATORS = {
  37. '+' => :plus,
  38. '-' => :minus,
  39. '*' => :times,
  40. '/' => :div,
  41. '%' => :mod,
  42. '=' => :single_eq,
  43. ':' => :colon,
  44. '(' => :lparen,
  45. ')' => :rparen,
  46. ',' => :comma,
  47. 'and' => :and,
  48. 'or' => :or,
  49. 'not' => :not,
  50. '==' => :eq,
  51. '!=' => :neq,
  52. '>=' => :gte,
  53. '<=' => :lte,
  54. '>' => :gt,
  55. '<' => :lt,
  56. '#{' => :begin_interpolation,
  57. '}' => :end_interpolation,
  58. ';' => :semicolon,
  59. '{' => :lcurly,
  60. '...' => :splat,
  61. }
  62. OPERATORS_REVERSE = Sass::Util.map_hash(OPERATORS) {|k, v| [v, k]}
  63. TOKEN_NAMES = Sass::Util.map_hash(OPERATORS_REVERSE) {|k, v| [k, v.inspect]}.merge({
  64. :const => "variable (e.g. $foo)",
  65. :ident => "identifier (e.g. middle)",
  66. :bool => "boolean (e.g. true, false)",
  67. })
  68. # A list of operator strings ordered with longer names first
  69. # so that `>` and `<` don't clobber `>=` and `<=`.
  70. OP_NAMES = OPERATORS.keys.sort_by {|o| -o.size}
  71. # A sub-list of {OP_NAMES} that only includes operators
  72. # with identifier names.
  73. IDENT_OP_NAMES = OP_NAMES.select {|k, v| k =~ /^\w+/}
  74. # A hash of regular expressions that are used for tokenizing.
  75. REGULAR_EXPRESSIONS = {
  76. :whitespace => /\s+/,
  77. :comment => COMMENT,
  78. :single_line_comment => SINGLE_LINE_COMMENT,
  79. :variable => /(\$)(#{IDENT})/,
  80. :ident => /(#{IDENT})(\()?/,
  81. :number => /(-)?(?:(\d*\.\d+)|(\d+))([a-zA-Z%]+)?/,
  82. :color => HEXCOLOR,
  83. :bool => /(true|false)\b/,
  84. :null => /null\b/,
  85. :ident_op => %r{(#{Regexp.union(*IDENT_OP_NAMES.map{|s| Regexp.new(Regexp.escape(s) + "(?!#{NMCHAR}|\Z)")})})},
  86. :op => %r{(#{Regexp.union(*OP_NAMES)})},
  87. }
  88. class << self
  89. private
  90. def string_re(open, close)
  91. /#{open}((?:\\.|\#(?!\{)|[^#{close}\\#])*)(#{close}|#\{)/
  92. end
  93. end
  94. # A hash of regular expressions that are used for tokenizing strings.
  95. #
  96. # The key is a `[Symbol, Boolean]` pair.
  97. # The symbol represents which style of quotation to use,
  98. # while the boolean represents whether or not the string
  99. # is following an interpolated segment.
  100. STRING_REGULAR_EXPRESSIONS = {
  101. [:double, false] => string_re('"', '"'),
  102. [:single, false] => string_re("'", "'"),
  103. [:double, true] => string_re('', '"'),
  104. [:single, true] => string_re('', "'"),
  105. [:uri, false] => /url\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/,
  106. [:uri, true] => /(#{URLCHAR}*?)(#{W}\)|#\{)/,
  107. # Defined in https://developer.mozilla.org/en/CSS/@-moz-document as a
  108. # non-standard version of http://www.w3.org/TR/css3-conditional/
  109. [:url_prefix, false] => /url-prefix\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/,
  110. [:url_prefix, true] => /(#{URLCHAR}*?)(#{W}\)|#\{)/,
  111. [:domain, false] => /domain\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/,
  112. [:domain, true] => /(#{URLCHAR}*?)(#{W}\)|#\{)/,
  113. }
  114. # @param str [String, StringScanner] The source text to lex
  115. # @param line [Fixnum] The line on which the SassScript appears.
  116. # Used for error reporting
  117. # @param offset [Fixnum] The number of characters in on which the SassScript appears.
  118. # Used for error reporting
  119. # @param options [{Symbol => Object}] An options hash;
  120. # see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
  121. def initialize(str, line, offset, options)
  122. @scanner = str.is_a?(StringScanner) ? str : Sass::Util::MultibyteStringScanner.new(str)
  123. @line = line
  124. @offset = offset
  125. @options = options
  126. @interpolation_stack = []
  127. @prev = nil
  128. end
  129. # Moves the lexer forward one token.
  130. #
  131. # @return [Token] The token that was moved past
  132. def next
  133. @tok ||= read_token
  134. @tok, tok = nil, @tok
  135. @prev = tok
  136. return tok
  137. end
  138. # Returns whether or not there's whitespace before the next token.
  139. #
  140. # @return [Boolean]
  141. def whitespace?(tok = @tok)
  142. if tok
  143. @scanner.string[0...tok.pos] =~ /\s\Z/
  144. else
  145. @scanner.string[@scanner.pos, 1] =~ /^\s/ ||
  146. @scanner.string[@scanner.pos - 1, 1] =~ /\s\Z/
  147. end
  148. end
  149. # Returns the next token without moving the lexer forward.
  150. #
  151. # @return [Token] The next token
  152. def peek
  153. @tok ||= read_token
  154. end
  155. # Rewinds the underlying StringScanner
  156. # to before the token returned by \{#peek}.
  157. def unpeek!
  158. @scanner.pos = @tok.pos if @tok
  159. end
  160. # @return [Boolean] Whether or not there's more source text to lex.
  161. def done?
  162. whitespace unless after_interpolation? && @interpolation_stack.last
  163. @scanner.eos? && @tok.nil?
  164. end
  165. # @return [Boolean] Whether or not the last token lexed was `:end_interpolation`.
  166. def after_interpolation?
  167. @prev && @prev.type == :end_interpolation
  168. end
  169. # Raise an error to the effect that `name` was expected in the input stream
  170. # and wasn't found.
  171. #
  172. # This calls \{#unpeek!} to rewind the scanner to immediately after
  173. # the last returned token.
  174. #
  175. # @param name [String] The name of the entity that was expected but not found
  176. # @raise [Sass::SyntaxError]
  177. def expected!(name)
  178. unpeek!
  179. Sass::SCSS::Parser.expected(@scanner, name, @line)
  180. end
  181. # Records all non-comment text the lexer consumes within the block
  182. # and returns it as a string.
  183. #
  184. # @yield A block in which text is recorded
  185. # @return [String]
  186. def str
  187. old_pos = @tok ? @tok.pos : @scanner.pos
  188. yield
  189. new_pos = @tok ? @tok.pos : @scanner.pos
  190. @scanner.string[old_pos...new_pos]
  191. end
  192. private
  193. def read_token
  194. return if done?
  195. return unless value = token
  196. type, val, size = value
  197. size ||= @scanner.matched_size
  198. val.line = @line if val.is_a?(Script::Node)
  199. Token.new(type, val, @line,
  200. current_position - size, @scanner.pos - size)
  201. end
  202. def whitespace
  203. nil while scan(REGULAR_EXPRESSIONS[:whitespace]) ||
  204. scan(REGULAR_EXPRESSIONS[:comment]) ||
  205. scan(REGULAR_EXPRESSIONS[:single_line_comment])
  206. end
  207. def token
  208. if after_interpolation? && (interp_type = @interpolation_stack.pop)
  209. return string(interp_type, true)
  210. end
  211. variable || string(:double, false) || string(:single, false) || number ||
  212. color || bool || null || string(:uri, false) || raw(UNICODERANGE) ||
  213. special_fun || special_val || ident_op || ident || op
  214. end
  215. def variable
  216. _variable(REGULAR_EXPRESSIONS[:variable])
  217. end
  218. def _variable(rx)
  219. line = @line
  220. offset = @offset
  221. return unless scan(rx)
  222. [:const, @scanner[2]]
  223. end
  224. def ident
  225. return unless scan(REGULAR_EXPRESSIONS[:ident])
  226. [@scanner[2] ? :funcall : :ident, @scanner[1]]
  227. end
  228. def string(re, open)
  229. return unless scan(STRING_REGULAR_EXPRESSIONS[[re, open]])
  230. if @scanner[2] == '#{' #'
  231. @scanner.pos -= 2 # Don't actually consume the #{
  232. @interpolation_stack << re
  233. end
  234. str =
  235. if re == :uri
  236. Script::String.new("#{'url(' unless open}#{@scanner[1]}#{')' unless @scanner[2] == '#{'}")
  237. else
  238. Script::String.new(@scanner[1].gsub(/\\(['"]|\#\{)/, '\1'), :string)
  239. end
  240. [:string, str]
  241. end
  242. def number
  243. return unless scan(REGULAR_EXPRESSIONS[:number])
  244. value = @scanner[2] ? @scanner[2].to_f : @scanner[3].to_i
  245. value = -value if @scanner[1]
  246. [:number, Script::Number.new(value, Array(@scanner[4]))]
  247. end
  248. def color
  249. return unless s = scan(REGULAR_EXPRESSIONS[:color])
  250. raise Sass::SyntaxError.new(<<MESSAGE.rstrip) unless s.size == 4 || s.size == 7
  251. Colors must have either three or six digits: '#{s}'
  252. MESSAGE
  253. value = s.scan(/^#(..?)(..?)(..?)$/).first.
  254. map {|num| num.ljust(2, num).to_i(16)}
  255. [:color, Script::Color.new(value)]
  256. end
  257. def bool
  258. return unless s = scan(REGULAR_EXPRESSIONS[:bool])
  259. [:bool, Script::Bool.new(s == 'true')]
  260. end
  261. def null
  262. return unless scan(REGULAR_EXPRESSIONS[:null])
  263. [:null, Script::Null.new]
  264. end
  265. def special_fun
  266. return unless str1 = scan(/((-[\w-]+-)?(calc|element)|expression|progid:[a-z\.]*)\(/i)
  267. str2, _ = Sass::Shared.balance(@scanner, ?(, ?), 1)
  268. c = str2.count("\n")
  269. old_line = @line
  270. old_offset = @offset
  271. @line += c
  272. @offset = (c == 0 ? @offset + str2.size : str2[/\n(.*)/, 1].size)
  273. [:special_fun,
  274. Sass::Util.merge_adjacent_strings(
  275. [str1] + Sass::Engine.parse_interp(str2, old_line, old_offset, @options)),
  276. str1.size + str2.size]
  277. end
  278. def special_val
  279. return unless scan(/!important/i)
  280. [:string, Script::String.new("!important")]
  281. end
  282. def ident_op
  283. return unless op = scan(REGULAR_EXPRESSIONS[:ident_op])
  284. [OPERATORS[op]]
  285. end
  286. def op
  287. return unless op = scan(REGULAR_EXPRESSIONS[:op])
  288. @interpolation_stack << nil if op == :begin_interpolation
  289. [OPERATORS[op]]
  290. end
  291. def raw(rx)
  292. return unless val = scan(rx)
  293. [:raw, val]
  294. end
  295. def scan(re)
  296. return unless str = @scanner.scan(re)
  297. c = str.count("\n")
  298. @line += c
  299. @offset = (c == 0 ? @offset + str.size : str[/\n(.*)/, 1].size)
  300. str
  301. end
  302. def current_position
  303. @offset + 1
  304. end
  305. end
  306. end
  307. end