gem-port

Port Ruby gems with native C extensions to Fil-C. Use when fixing gem compilation errors, VALUE/int type mismatches, rb_attr/rb_protect issues, or using ast-grep for Ruby C extensions.

$ 安裝

git clone https://github.com/mbrock/filnix /tmp/filnix && cp -r /tmp/filnix/.claude/skills/gem-port ~/.claude/skills/filnix

// tip: Run this command in your terminal to install the skill


name: gem-port description: Port Ruby gems with native C extensions to Fil-C. Use when fixing gem compilation errors, VALUE/int type mismatches, rb_attr/rb_protect issues, or using ast-grep for Ruby C extensions.

Fil-C Ruby Gem Porting

Context

Fil-C is a memory-safe C compiler. Ruby's VALUE type becomes a pointer (struct rb_value_unit_struct *) instead of an integer. This breaks code that:

  • Passes int literals where VALUE expected (Qfalse, Qtrue, ERROR_TOKEN)
  • Stores VALUE in int variables
  • Does bitwise ops on VALUE
  • Mixes VALUE and ID types
  • Uses switch on VALUE (case labels must be integer constants)

Workflow

Each command runs independently (no persistent shell). Use ruby_3_3 not ruby.

1. Unpack gem

rm -rf /tmp/gem-port && mkdir -p /tmp/gem-port
nix develop .#pkgsFilc.ruby_3_3.gems.<name> --command bash -c 'gem unpack $src'
# Move to /tmp if unpacked in current dir
mv *<name>* /tmp/gem-port/ 2>/dev/null || true

2. Find ext structure

ls /tmp/gem-port/*/ext/

Note: Some gems have ext/<name>/, others have files directly in ext/.

3. Run extconf.rb

nix develop .#pkgsFilc.ruby_3_3.gems.<name> --command bash -c \
  'cd /tmp/gem-port/*/ext && ruby extconf.rb'
# Or if nested: cd /tmp/gem-port/*/ext/<name>

4. Build and capture errors

nix develop .#pkgsFilc.ruby_3_3.gems.<name> --command bash -c \
  'cd /tmp/gem-port/*/ext && make 2>&1' > /tmp/build.log
cat /tmp/build.log | head -100

5. Fix and iterate

Edit files in /tmp/gem-port/*/ext/ directly, then re-run step 4. No need to re-unpack or re-run extconf.

6. Add to rubyports.nix and verify

# Always capture stderr and check exit code
nix build .#pkgsFilc.ruby_3_3.gems.<name> 2>&1; echo "EXIT: $?"
ls result/ 2>&1

ast-grep Usage

ALWAYS use ast-grep for pattern-based code fixes. It handles whitespace variations that break string replacement.

Selectors

Different C constructs need different selectors:

  • call_expression - function calls like rb_attr($A, $B, Qfalse)
  • switch_statement - switch blocks with case labels
  • declaration - variable declarations

Examples

Function call replacement:

ast-grep run -p 'rb_attr($A, $B, $C, $D, Qfalse)' \
             -r 'rb_attr($A, $B, $C, $D, 0)' \
             -l c --selector call_expression -U .

Switch to if-else (VALUE can't be case label):

ast-grep run \
  -p 'switch (rb_range_beg_len($A, $B, $L, $S, $F)) { case Qfalse: break; case Qnil: return Qnil; default: return subseq($X, $Y, $Z); }' \
  -r '{ VALUE r = rb_range_beg_len($A, $B, $L, $S, $F); if (r == Qfalse) { } else if (r == Qnil) { return Qnil; } else { return subseq($X, $Y, $Z); } }' \
  -l c --selector switch_statement -U .

Finding patterns (no -U):

ast-grep run -p 'static VALUE $NAME;' -l c .
ast-grep run -p 'case Q$CONST:' -l c .

rubyports.nix Helpers

# String replacement (fragile - prefer ast-grep)
(replace "path/to/file.c" "old string" "new string")

# ast-grep with call_expression selector
(astGrep "pattern($A)" "replacement($A)")

# ast-grep with custom selector
(astGrepSel "switch_statement" "switch(...)" "if-else...")

Common Patterns

IssuePatternFix
VALUE→ID for rb_internstatic VALUE id = rb_intern(...)static ID id = ...
int→VALUE in switchcase Qfalse: case Qnil:Convert to if-else
VALUE reused for intarg = ... (bitwise op)Use separate int variable
int return from VALUE funcreturn 1; in VALUE functionreturn Qtrue;

See REFERENCE.md for full patterns, FIXES.md for per-gem solutions, and AST_GREP.md for comprehensive ast-grep documentation.