2015-10-31 12:18:18 -04:00
|
|
|
require 'ripper'
|
|
|
|
|
|
|
|
module Hamlit
|
|
|
|
class HashParser
|
2015-11-03 00:32:14 -05:00
|
|
|
class ParseSkip < StandardError
|
|
|
|
end
|
|
|
|
|
2015-10-31 12:18:18 -04:00
|
|
|
def self.parse(text)
|
|
|
|
self.new.parse(text)
|
|
|
|
end
|
|
|
|
|
|
|
|
def parse(text)
|
2015-10-31 21:55:15 -04:00
|
|
|
exp = '{' << text.strip << '}'.freeze
|
|
|
|
return if syntax_error?(exp)
|
|
|
|
|
2015-10-31 12:18:18 -04:00
|
|
|
hash = {}
|
2015-10-31 21:55:15 -04:00
|
|
|
tokens = Ripper.lex(exp)[1..-2] || []
|
2015-10-31 12:18:18 -04:00
|
|
|
each_attr(tokens) do |attr_tokens|
|
|
|
|
key = parse_key!(attr_tokens)
|
|
|
|
hash[key] = attr_tokens.map(&:last).join.strip
|
|
|
|
end
|
|
|
|
hash
|
2015-11-03 00:32:14 -05:00
|
|
|
rescue ParseSkip
|
|
|
|
nil
|
2015-10-31 12:18:18 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def parse_key!(tokens)
|
|
|
|
_, type, str = tokens.shift
|
|
|
|
case type
|
|
|
|
when :on_sp
|
|
|
|
parse_key!(tokens)
|
|
|
|
when :on_label
|
2015-11-03 00:32:14 -05:00
|
|
|
str.tr(':'.freeze, ''.freeze)
|
2015-11-01 06:08:04 -05:00
|
|
|
when :on_symbeg
|
|
|
|
_, _, key = tokens.shift
|
2015-11-03 00:32:14 -05:00
|
|
|
assert_type!(tokens.shift, :on_tstring_end) if str != ':'.freeze
|
|
|
|
skip_until_hash_rocket!(tokens)
|
|
|
|
key
|
|
|
|
when :on_tstring_beg
|
|
|
|
_, _, key = tokens.shift
|
|
|
|
next_token = tokens.shift
|
|
|
|
unless next_token[1] == :on_label_end
|
|
|
|
assert_type!(next_token, :on_tstring_end)
|
|
|
|
skip_until_hash_rocket!(tokens)
|
2015-11-01 06:08:04 -05:00
|
|
|
end
|
|
|
|
key
|
2015-10-31 12:18:18 -04:00
|
|
|
else
|
2015-11-03 00:55:39 -05:00
|
|
|
raise ParseSkip
|
2015-10-31 12:18:18 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-11-03 00:32:14 -05:00
|
|
|
def assert_type!(token, type)
|
|
|
|
raise ParseSkip if token[1] != type
|
|
|
|
end
|
|
|
|
|
|
|
|
def skip_until_hash_rocket!(tokens)
|
|
|
|
until tokens.empty?
|
|
|
|
_, type, str = tokens.shift
|
|
|
|
break if type == :on_op && str == '=>'.freeze
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-10-31 12:18:18 -04:00
|
|
|
def each_attr(tokens)
|
|
|
|
attr_tokens = []
|
2015-11-03 00:55:39 -05:00
|
|
|
array_open = 0
|
|
|
|
|
2015-10-31 12:18:18 -04:00
|
|
|
tokens.each do |token|
|
|
|
|
(row, col), type, str = token
|
|
|
|
case type
|
|
|
|
when :on_comma
|
2015-11-03 00:55:39 -05:00
|
|
|
if array_open == 0
|
|
|
|
yield(attr_tokens)
|
|
|
|
attr_tokens = []
|
|
|
|
next
|
|
|
|
end
|
|
|
|
when :on_lbracket
|
|
|
|
array_open += 1
|
|
|
|
when :on_rbracket
|
|
|
|
array_open -= 1
|
2015-10-31 12:18:18 -04:00
|
|
|
end
|
2015-11-03 00:55:39 -05:00
|
|
|
|
|
|
|
attr_tokens << token
|
2015-10-31 12:18:18 -04:00
|
|
|
end
|
|
|
|
yield(attr_tokens) unless attr_tokens.empty?
|
|
|
|
end
|
2015-10-31 21:55:15 -04:00
|
|
|
|
|
|
|
def syntax_error?(code)
|
|
|
|
ParseErrorChecker.new(code).parse
|
|
|
|
false
|
|
|
|
rescue ParseErrorChecker::ParseError
|
|
|
|
true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class ParseErrorChecker < Ripper
|
|
|
|
class ParseError < StandardError
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def on_parse_error(*)
|
|
|
|
raise ParseError
|
|
|
|
end
|
2015-10-31 12:18:18 -04:00
|
|
|
end
|
|
|
|
end
|