Understanding Ruby's Grammar: Why Pure Sentence Syntax Fails

Ruby is famous for its expressive syntax and capability for creating Domain-Specific Languages (DSLs). However, trying to write a sentence like girl "Alice" with age 20 go to :home without any commas, dots, or operators triggers a Ruby syntax error (unexpected tIDENTIFIER).

This happens because of how Ruby's parser processes expressions:

  • Method Calls: Ruby allows omitting parentheses on method calls (e.g., girl "Alice"), but consecutive expressions require a explicit delimiter (like a comma or semicolon) or an operator between them.
  • Receiver and Methods: To call a method on an object without an operator, Ruby requires a dot (.), as in object.method.

While you cannot eliminate delimiters entirely in standard Ruby without external code preprocessing, you can achieve near-sentence syntax using idiomatic Ruby DSL design patterns.

Approach 1: Method Chaining (The Fluent Interface)

The closest standard syntax to what you attempted uses method chaining. By returning self from intermediate methods, you can chain words together sequentially.

class PersonDSL
  attr_reader :name, :person_age, :destination

  def initialize(name)
    @name = name
  end

  def with
    self
  end

  def age(value)
    @person_age = value
    self
  end

  def go
    self
  end

  def to(location)
    @destination = location
    self
  end
end

def girl(name)
  PersonDSL.new(name)
end

# Usage:
action = girl("Alice").with.age(20).go.to(:home)
puts "#{action.name}, age #{action.person_age}, is going to #{action.destination}"

Approach 2: Operator Overloading

If your goal is to avoid dots (.), you can overload operators such as +, >>, or / to act as glue between sentence components.

class SentenceBuilder
  attr_reader :data

  def initialize(initial_val)
    @data = [initial_val]
  end

  def >>(other)
    @data << other
    self
  end
end

def girl(name)
  SentenceBuilder.new(name)
end

def with(val)
  { with: val }
end

def age(val)
  { age: val }
end

# Usage using the >> operator as a separator:
girl "Alice" >> with(age(20)) >> :home

Approach 3: Clean Block-Based DSL (The Idiomatic Ruby Way)

In production Ruby code (such as Rails or RSpec), sentence-like structures are typically handled using blocks and instance_eval. This removes the need for dots between chained calls while remaining valid Ruby syntax.

class GirlContext
  attr_reader :name, :age_val, :location

  def initialize(name, &block)
    @name = name
    instance_eval(&block) if block_given?
  end

  def with_age(number)
    @age_val = number
  end

  def go_to(destination)
    @location = destination
  end
end

def girl(name, &block)
  GirlContext.new(name, &block)
end

# Usage:
subject = girl "Alice" do
  with_age 20
  go_to :home
end

puts "#{subject.name} (age #{subject.age_val}) -> #{subject.location}"

Summary

Due to Ruby's grammar, a completely separator-free syntax like girl "Alice" with age 20 go to :home is impossible without a custom pre-parser. For real-world Ruby projects, standard method chaining or block-based DSLs provide the best balance of readability and syntax validity.