Back to all posts
3 min read

CrystalMeet

CrystalMeet

This is the second library from Matz Told Me Why, after macroaggression. It won’t get a repository link, because the code doesn’t deserve one.

What it was

CrystalMeet was a Rust command-line tool. The name means Crystal meets Ruby, though CrystalMeth is the cooler reading. I wrote it to learn Clap, the argument parser most Rust CLIs start with. These days I use jdx’s usage instead.

It read Ruby and wrote Crystal. Crystal borrows Ruby’s syntax and infers static types on its own, so a lot of Ruby is almost Crystal already. CrystalMeet handled the almost with a pile of regular expressions.

It half worked. Plenty of scripts came out as working Crystal, in ugly code.

The runtime is the wall

The other half ran into Ruby’s runtime. A Ruby program can create classes while it runs, and a regex only ever sees the source.

closure_tree, a gem I co-maintain, shows it. When a model calls has_closure_tree, the gem builds a second model for the hierarchy table, on the fly:

def hierarchy_class_for_model
  parent_class = model_class.module_parent
  hierarchy_class = parent_class.const_set(short_hierarchy_class_name, Class.new(abstract_base_class))
  model_class_name = model_class.to_s
  hierarchy_class.class_eval do
    # ...
    belongs_to :ancestor, class_name: model_class_name
    belongs_to :descendant, class_name: model_class_name
    # ...
  end
  # ...
end

For a Tag model the class is TagHierarchy, a name the gem builds from a string. No source file defines it, and finding it means running the gem’s Ruby code. Crystal could build a name like that with a macro at compile time. A regex over Ruby source cannot. ActiveRecord goes further and defines each model’s attribute methods from the database schema, which no Ruby file contains.

A proper parser would not have saved CrystalMeet either. spinel, matz’s ahead-of-time Ruby compiler, reads Ruby through Prism, the parser CRuby ships, and its README says plainly: “No dynamic metaprogramming or eval.” I gave it the pattern in a few lines:

class Tag
end
name = "#{Tag}Hierarchy"
klass = Object.const_set(name, Class.new)
klass.class_eval do
  def depth = 1
end
p Object.const_get(name).new.depth

Ruby prints 1. spinel stops at the line that looks the class up by name:

const_get with a name known only at run time is not supported: constants are resolved at compile time (use a literal name, or a Hash from names to classes)

The same program with class TagHierarchy written out compiles and prints 1. One of the tests I added to spinel lives in test/reject/, where every file is a program spinel has to refuse. It adds a method to Class inside a method body, and that method only exists once the body runs.

The tests were the good part

What CrystalMeet did have was tests, about 200 scenarios. Two years later they are the only part worth keeping, because they record what Ruby does at its edges. spinel translates Ruby too, into C, and needs exactly that list.

Where they went

Since September 24 I have been porting those scenarios into spinel. As of September 29:

Commits in spinel129+
Pull requests closed without merging1, #4951
New files138+, all of them tests
Rank among 68 contributors, by commits7th

Matz merged every pull request of mine I checked.

The titles read like scenarios. One says “An empty **h passes no argument: a keyword list of only spreads rides a *rest or Proc#call only when it has entries.” Another says “A new local starts with every flag cleared, so the same program compiles to the same C every run.” More than half of those commits add or change a test. Most of the rest fold duplicated code in the compiler into shared helpers.

The regexes are gone and nobody will miss them.

🔗Interstellar Communications

No transmissions detected yet.Be the first to establish contact!

• Link to this post from your site• Share your thoughts via webmention• Join the IndieWeb conversation

Related Posts

Macroaggression

I abused the C preprocessor until C read like Ruby. At -O2 the assembly shows it changes little beyond the spelling of a loop. Steve Bourne did the same to C in the 1970s, and his version inspired the International Obfuscated C Code Contest.

crubyopen-source

Matz Told Me Why

In one week, matz closed my pull request to spinel and the /r/rails moderators banned me for good. Matz explained his decision in five sentences. In two years of removing my posts, the moderators never explained one.

rubyrailsopen-source

The Rust Revolution Proposal Had a 363-Day Fuse

In September 2025 I wrote a satirical proposal to rewrite Rails in Rust, and picked Rust because it was the one language David would never touch. On September 23, 2026 he told Rails World he hates Rust with a passion and is rebuilding HEY's backend in it. The satire got one line wrong, and it stopped one language too early.

rubyrailsrust