Here’s one more ParseTree based technique for ordered hashes, it’ll be the last one, I promise. This one doesn’t require any Ruby2Ruby string building/evaling craziness. The syntax is the same as the previous ParseTree example:
hash = H{{ :key1 => value1, 'key2' => value2 }}
But with this implementation you can only use literals as keys, meaning strings, symbols, and numbers.
First we call the block with yield to get the original unordered hash. Next we use ParseTree just to find the keys of the hash in order. Since we only support literals we can use the value straight out of the s-expression, no conversion is needed. And we don’t need to look at the keys’ values in the s-expression since we already have them in the unordered hash. Finally, we create a new ordered hash and set each key in order, grabbing the value from the original (unordered) hash. This allows us to simplify the use of the s-expressions from ParseTree and not have to deal with code generation at all (Ruby2Ruby). It also gives about a 4x speedup over the previous technique.
Here’s the code:
class K;end
def H(&b)
K.send :define_method, :x, &b
sexp = ParseTree.translate(K, :x)
hash_sexp = sexp[2][2] # skip method definition stuff
raise "block doesn't contain hash!" if hash_sexp.first != :hash
hash_sexp.slice! 0
unordered_hash = yield
oh = OrderedHash.new
until hash_sexp.empty?
(type, k), _ = hash_sexp.slice! 0, 2
raise "bad key type: #{type}" if ! [:lit, :str].include?(type)
oh[k] = unordered_hash[k]
end
oh
end
This and alternative implementations are on my github.
