1

I'm new to ruby so please excuse my ignorance :)

I just learned about eval and I read about its dark sides.

what I've read so far:

so what I have to do is to read a file in which there are some text such as /e/ 3 which will replace each e with a 3 after evaluation.

so here what i did so far:(working but..)

def evaluate_lines
  result="elt"
  IO.foreach("test.txt") do |reg|
    reg=reg.chomp.delete(' ')
    puts reg
    result=result.gsub(eval(reg[0..2]),"#{reg[3..reg.length]}" )
    p result
  end
end

contents of the test.txt file

/e/ 3
/l/ 1
/t/ 7
/$/ !
/$/ !!

this only works because I know the length of the lines in the file.

so assuming my file has the following /a-z/ 3 my program would be not able to do what is expected from it.

Note

I tried using Regexp.new reg and this resulted with the following /\/e\/3/ which isn't very helpful in this case.

simple example to the `Regexp

str="/e/3"
result="elt"
result=result.gsub(Regexp.new str)
p result #outputs: #<Enumerator: "elt":gsub(/\/e\/3/)>

i already tried stripping off the slashes but even though this wont deliver the desired result thus the gsub() takes two parameters, such as this gsub(/e/, "3").

for the usage of the Regexp, I have already read Convert a string to regular expression ruby

StudentAccount4
  • 186
  • 1
  • 11
  • 1
    You don't need `eval` when you have [`Regex.new`](https://ruby-doc.org/core-2.7.1/Regexp.html#method-c-new). I'd also steer far away from naming your input file `.rb` as it is most certainly not Ruby code. To make it work with `Regexp.new` *strip off the leading and trailing slashes* first. – tadman May 10 '20 at 22:28
  • @tadman, it's my bad I typed it rb instead of txt sorry. I edited the post. anyway, using the regexp.new didn't help thus I will give me the undesired result because the method `gsub` needs two parameters such as `gsub(/e/, "3")` – StudentAccount4 May 10 '20 at 22:40
  • 1
    Do you have control over the format of the input file? If so you could make your life a hundred times easier. – tadman May 10 '20 at 22:40
  • well, while I have total control over this file but the format is given by the assignment maker. and [this](https://stackoverflow.com/a/41306390/11618992) is not on the table thus the test.txt could be bigger when testing :). again even if i could strip off the back slashes i have to give the second parameter to the gsub method – StudentAccount4 May 10 '20 at 22:49

2 Answers2

2

Here's a really simple example that uses vi notation like s/.../.../ and s/.../.../g:

def rsub(text, spec)
  _, mode, repl, with, flags = spec.match(%r[\A(.)\/((?:[^/]|\\/)*)/((?:[^/]|\\/)*)/(\w*)\z]).to_a

  case (mode)
  when 's'
    if (flags.include?('g'))
      text.gsub(Regexp.new(repl), with)
    else
      text.sub(Regexp.new(repl), with)
    end
  end
end

Note the matcher looks for non-slash characters ([^/]) or a literal-slash combination (\\/) and splits out the two parts accordingly.

Where you can get results like this:

rsub('sandwich', 's/and/or/')
# => "sorwich"

rsub('and/or', 's/\//,/')
# => "and,or"

rsub('stack overflow', 's/o/O/')
# => "stack Overflow"

rsub('stack overflow', 's/o/O/g')
# => "stack OverflOw"

The principle here is you can use a very simple regular expression to parse out your input regular expression and feed that cleaned up data into Regexp.new. There is absolutely no need for eval here, and if anything that severely limits what you can do.

With a little work you could alter that regular expression to parse what's in your existing file and make it do what you want.

tadman
  • 208,517
  • 23
  • 234
  • 262
  • okay this looks cool and I think i got what you are trying to accomplish here but I have a small question about the usage of the `_,`what is this for?! plus, eyeballing the code you wrote I have a small question and please excuse my ignorance how are the return values of the spec.match distributed among the mode, repl, with?! would you please be so kind and write a detailed explanation about what is going on in the second line of the `rsub()` otherwise this should help :) – StudentAccount4 May 10 '20 at 23:12
  • In Ruby `_` is a "don't care" variable. The first element is irrelevant, it's the set of all matches. As for the explanation, can't make it too easy for you, there's a lot going on there but if you break it down and experiment with that line in `irb` it'll make more sense. – tadman May 10 '20 at 23:19
2

While you can write something to parse that file, it rapidly gets complicated because you have to parse regular expressions. Consider /\/foo\\/.

There are a number of incomplete solutions. You can split on whitespace, but this will fail on /foo bar/.

re, replace = line.split(/\s+/, 2)

You can use a regex. Here's a first stab.

match = "/3/ 4".match(%r{^/(.*)/\s+(.+)})

This fails on escaped /, we need something more complex.

match = '/3\// 4'.match(%r{\A / ((?:[^/]|\\/)*) / \s+ (.+)}x)

I'm going to guess it was not your teacher's intent to have you parsing regexes. For the purposes of the assignment, splitting on whitespace is probably fine. You should clarify with your teacher.


This is a poor data format. It is non-standard, difficult to parse, and has limitations on the replacement. Even a tab-delimited file would be better.

There's little reason to use a non-standard format these days. The simplest thing is to use a standard data format for the file. YAML or JSON are the most obvious choices. For such simple data, I'd suggest JSON.

[
  { "re": "e", "replace": "3" },
  { "re": "l", "replace": "1" }
]

Parsing the file is trivial, use the built-in JSON library.

require 'json'
specs = JSON.load("test.json")

And then you can use them as a list of hashes.

specs.each do |spec|
  # No eval necessary.
  re = Regexp.new(spec["re"])

  # `gsub!` replaces in place
  result.gsub!(re, spec["replace"])
end

The data file is extensible. For example, if later you want to add regex options.

[
  { "re": "e", "replace": "3" },
  { "re": "l", "replace": "1", "options": ['IGNORECASE'] }
]

While the teacher may have specified a poor format, pushing back on bad requirements is good practice for being a developer.

Schwern
  • 153,029
  • 25
  • 195
  • 336
  • thanks for taking the effort to help man. small chit-chat about your code: well as a student and ruby beginner i have never worked with the JSON library yet. but looking at your code I kinda understand what is happening in the JSON part of your answer. so, lemme see if I got this straight: 1.you would write over, all modifications from the test.txt file and put it in JSON file with a different format? 2.you would take the data from the txt and put it in hash then iterate through it? so where are those `[ { "re": "e", "replace": "3" }, { "re": "l", "replace": "1" } ]` written? – StudentAccount4 May 10 '20 at 23:37
  • anyway I will ask the professor again to get a better idea about his intentions then i can see if this answer would be the one but for now I'm very thankful for your help and +1 – StudentAccount4 May 11 '20 at 00:00
  • @studentaccount4 Yes, if this were production I'd change the file to JSON. You can translate it by hand, like I did. Or, since the data is so simple, use the regex to parse the original file, put it into a sensible data structure, and write it out as JSON with `JSON.dump`. As this is an assignment, yes, ask your professor what they'd like you to do. – Schwern May 11 '20 at 06:35