1

I'm trying to write an internal DSL in Ruby and am running into trouble with implementing a particular syntax. Specifically, if I have an array (or hash) in a class, I'd like to access and edit it with parentheses. For example:

class MyData
  attr_accessor :things
  def initialize
    @things = ['right', 'right', 'wrong']
  end
end

test = MyData.new

# This obviously won't work, but it's the syntax that I want.
test.things(2) = 'right'

I know I can read an element with this syntax by doing:

class MyData
  def things(index)
    @things[index]
  end
end

test = MyData.new
test.things(2) # => 'wrong' 

but actually changing the element is another thing entirely because Ruby just doesn't know what to do with that assignment operator.

The reason I want this strange syntax is because I hope I can use this language to easily convert some Fortran namelist files into a friendly Ruby environment, and Fortran unfortunately indexes arrays with parentheses.

My fear is that this is just one of those situations where I'm trying too hard to make Ruby go against its core syntax and I'll need to actually write a parser. Any thoughts?

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
wmwolf
  • 63
  • 4
  • 3
    That can't, and shouldn't work. It's a syntax error. More over, it *looks* dead wrong to an experienced programmer. That isn't syntax that *should* work. You could produce something like in in C++ where you can define a custom assignment operator and return a reference to a wrapper object, but even then, you *shouldn't* do it. – user229044 Jan 10 '14 at 05:40
  • @meagar, I know and totally agree that this is terrible ruby syntax, but as I stated, this is for a DSL (I have absolutely no desire for the confusion this would cause in regular old ruby code, but it would be tremendously useful and even natural in the context I want to use it). Unfortunately, this simplified explanation makes it look completely indefensible. I just wondered if anyone knew of a dirty hack to make that work for my interests in this project. Thanks for your input! – wmwolf Jan 10 '14 at 08:07
  • As I said, it *can't*. This is a syntax error. Ruby cannot run this code. No amount of monkey patching or meta programming can make this work. – user229044 Jan 10 '14 at 08:08
  • It's not "terrible Ruby syntax". It's "not Ruby syntax". Period. You cannot write code that isn't Ruby in Ruby. – Jörg W Mittag Jan 10 '14 at 12:18

2 Answers2

1

The syntax isn't valid Ruby, ergo you cannot do that in Ruby. Period.

You have several options:

  1. Change your syntax to something that is valid Ruby:

    class MyData
      attr_reader :things
    
      private
    
      attr_writer :things
    
      def initialize
        self.things = %w[right right wrong]
      end
    end
    
    test = MyData.new
    
    test.things[2] = 'right'
    
    test.things
    # => ['right', 'right', 'right']
    
  2. Use a language that supports that syntax, like Scala:

    class MyData {
      var things = scala.collection.mutable.Seq("right", "right", "wrong")
    }
    
    val test = new MyData
    
    test.things(2) = "right"
    
    test.things
    // => scala.collection.mutable.Seq[String] = ArrayBuffer(right, right, right)
    
  3. Create an external DSL, i.e. one that isn't valid Ruby.

    In fact, it looks like what you have already is a well-defined external DSL, specifically a subset of Fortran. Depending on whether or not you have control over those namelist files, you may even be able to restrict the syntax further to something that is quite trivially parseable with some Regexps and a bit of text munging.

Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
  • Thanks for laying out the options. I'm going with option 1 for now, but some day in the future I might poke around with option 2 or 3. – wmwolf Jan 10 '14 at 22:40
0

The call operator () cannot be overloaded so what you want to do technically cannot be done. An option would be to pre-process your DSL to replace parentheses with brackets before you hand it over to Ruby.

Check out this other answer on operator overloading for more info.

Community
  • 1
  • 1
Matt
  • 800
  • 1
  • 7
  • 15