Ruby StringScanner used for lexing : how to get the line number? - ruby

I am using StringScanner for lexical analysis like this :
def next
#scanner.skip(/\s+/)
value,kind=nil,nil
TOKEN_DEF.each{|tok,regex| (kind=tok;break) if #scanner.scan(regex)}
return Token.new(kind,value,#line,#scanner.pos)
end
At first approximation, this works well, except that I can't figure out how to now get the #line number.
I have read the doc, where begin_of_line? method seems appropriate, but I cannot figure how to use it.

Keep the text that you are scanning in a variable and use 'count'
I use the following in my code:
def current_line_number; #text[0..#scanner.pos].count("\n") + 1; end

This code doesn't seem ready to go and for sure somewhere else more elegant solution, it just should give you something to think about.
class Retry < StandardError
end
class TextScanner
def initialize(filename)
#lines = IO.readlines(filename)
#fiber = Fiber.new do
#lines.each_with_index do |line, index|
#scanner = StringScanner.new(line)
#scanner.skip(/\s+/)
value, kind = nil, nil
begin
got_token = false
TOKEN_DEF.each do |tok, regex|
if #scanner.scan(regex)
Fiber.yield Token.new(tok, value, index, #scanner.pos)
got_token = true
end
end
raise Retry if got_token
rescue Retry
retry
end
end
"fiber is finished"
end
end
def next
#fiber.resume
end
end
text_scanner = TextScanner('sometextfile')
puts text_scanner.next #=> first token
puts text_scanner.next #=> second token
puts text_scanner.next #=> third token
...
puts text_scanner.next #=> "fiber is finished"

I think I have a simple solution. Here it is :
def next
#line+=1 while #scanner.skip(/\n/)
#line+=1 if #scanner.bol?
#scanner.skip(/\s+/)
#line+=1 if #scanner.bol?
#scanner.skip(/\s+/)
return :eof if #scanner.eos?
TOKEN_DEF.each { |tok,syntax| (kind=tok;break) if #scanner.scan(syntax)}
return Token.new(kind,nil,#line,#scanner.pos)
end

Related

Get line number of beginning and end of Ruby method given a ruby file

How can I find the line of the beginning and end of a Ruby method given a ruby file?
Say for example:
1 class Home
2 def initialize(color)
3 #color = color
4 end
5 end
Given the file home.rb and the method name initialize I would like to receive (2,4) which are the beginning and end lines.
Finding the end is tricky. The best way I can think of is to use the parser gem. Basically you'll parse the Ruby code into an AST, then recursively traverse its nodes until you find a node with type :def whose first child is :initialize:
require "parser/current"
def recursive_find(node, &block)
return node if block.call(node)
return nil unless node.respond_to?(:children) && !node.children.empty?
node.children.each do |child_node|
found = recursive_find(child_node, &block)
return found if found
end
nil
end
src = <<END
class Home
def initialize(color)
#color = color
end
end
END
ast = Parser::CurrentRuby.parse(src)
found = recursive_find(ast) do |node|
node.respond_to?(:type) && node.type == :def && node.children[0] == :initialize
end
puts "Start: #{found.loc.first_line}"
puts "End: #{found.loc.last_line}"
# => Start: 2
# End: 4
P.S. I would have recommended the Ripper module from the standard library, but as far as I can tell there's no way to get the end line out of it.
Ruby has a source_location method which gives you the file and the beginning line:
class Home
def initialize(color)
#color = color
end
end
p Home.new(1).method(:initialize).source_location
# => ["test2.rb", 2]
To find the end, perhaps look for the next def or EOF.
Ruby source is nothing but a text file. You can use linux commands to find the method line number
grep -nrw 'def initialize' home.rb | grep -oE '[0-9]+'
I have assumed that the file contains the definition of at most one initialize method (though generalizing the method to search for others would not be difficult) and that the definition of that method contains no syntax errors. The latter assumption is probably required for any method to extract the correct line range.
The only tricky part is finding the line containing end that is the last line of the definition of the initialize method. I've used Kernel#eval to locate that line. Naturally caution must be exercised whenever that method is to be executed, though here eval is merely attempting to compile (not execute) a method.
Code
def get_start_end_offsets(fname)
start = nil
str = ''
File.foreach(fname).with_index do |line, i|
if start.nil?
next unless line.lstrip.start_with?('def initialize')
start = i
str << line.lstrip.insert(4,'_')
else
str << line
if line.strip == "end"
begin
rv = eval(str)
rescue SyntaxError
nil
end
return [start, i] unless rv.nil?
end
end
end
nil
end
Example
Suppose we are searching a file created as follows1.
str = <<-_
class C
def self.feline
"cat"
end
def initialize(arr)
#row_sums = arr.map do |row|
row.reduce do |t,x|
t+x
end
end
end
def speak(sound)
puts sound
end
end
_
FName = 'temp'
File.write(FName, str)
#=> 203
We first search for the line that begins (after stripping leading spaces) "def initialize". That is the line at index 4. The end that completes the definition of that method is at index 10. We therefore expect the method to return [4, 10].
Let's see if that's what we get.
p get_start_end_offsets(FName)
#=> [4, 10]
Explanation
The variable start equals the index of the line beginning def initialize (after removing leading whitespace). start is initially nil and remains nil until the "def initialize" line is found. start is then set to the index of that line.
We now look for a line line such that line.strip #=> "end". This may or may not be the end that terminates the method. To determine if it is we eval a string that contains all lines from the one that begins def initialize to the line equal to end just found. If eval raises a SyntaxError exception that end does not terminate the method. That exception is rescued and nil is returned. eval will return :_initialize (which is truthy) if that end terminates the method. In that case the method returns [start, i], where i is the index of that line. nil is returned if no initialize method is found in the file.
I've converted "initialize" to "_initialize" to suppress the warning (eval):1: warning: redefining Object#initialize may cause infinite loop)
See both answers to this SO question to understand why SyntaxError is being rescued.
Compare indentation
If it is known that "def initialize..." is always indented the same amount as the line "end" that terminates the method definition (and no other lines "end" between the two are indented the same), we can use that fact to obtain the beginning and ending lines. There are many ways to do that; I will use Ruby's somewhat obscure flip-flop operator. This approach will tolerate syntax errors.
def get_start_end_offsets(fname)
indent = -1
lines = File.foreach(fname).with_index.select do |line, i|
cond1 = line.lstrip.start_with?('def initialize')
indent = line.size - line.lstrip.size if cond1
cond2 = line.strip == "end" && line.size - line.lstrip.size == indent
cond1 .. cond2 ? true : false
end
return nil if lines.nil?
lines.map(&:last).minmax
end
get_start_end_offsets(FName)
#=> [4, 10]
1 The file need not contain only code.

Rspec test cases not working

I probably have done some silly mistake but by running rake shows this
Due to infinite loop it runs infinitely until i stopped it.
The folder structure follows :
calculator.rb
class Calculator
attr_accessor :result
def initialize
#result = 0.0
end
def add(param)
#result += param
end
def subtract(param)
#result -= param
end
def multiply(param)
#result *= param
end
def divide(param)
#result /= param
end
def cancel
#result = 0.0
end
end
class CommandProcessor
attr_accessor :input
attr_accessor :operation
attr_accessor :calculator
def parser
calculator = Calculator.new
while true
input = gets.to_s.chomp
operation = input.split(' ')[0]
param = input.split(' ')[1]
if operation.eql? 'exit'
exit
elsif operation.eql? 'add'
calculator.add(param.to_f)
puts calculator.result
elsif operation.eql? 'subtract'
calculator.subtract(param.to_f)
puts calculator.result
elsif operation.eql? 'multiply'
calculator.multiply(param.to_f)
puts calculator.result
elsif operation.eql? 'divide'
calculator.divide(param.to_f)
puts calculator.result
elsif operation.eql? 'cancel'
calculator.cancel
puts calculator.result
else
puts "invalid op"
end
end
end
end
command = CommandProcessor.new
command.parser
calculator_spec.rb
require 'spec_helper'
require "calculator.rb"
describe "CommandProcessor" do
it "will exit on input exit" do
#cmd = CommandProcessor.new
#cmd.stub!(:gets).and_return("add 3\n")
#cmd.parser
expect(#cmd.calculator.result).to eq 3
end
end
describe "Calculator" do
it "will add a number" do
calculator = Calculator.new
expect(calculator.add 2).to eq 2.0
expect(calculator.add 2.0).to eq 4.0
end
it "will subtract a number" do
calculator = Calculator.new
expect(calculator.subtract 2).to eq -2.0
expect(calculator.subtract 2.0).to eq -4.0
end
it "will multiply a number" do
calculator = Calculator.new
expect(calculator.multiply 2).to eq 0.0
expect(calculator.multiply 2.0).to eq 0.0
end
it "will divide a number" do
calculator = Calculator.new
expect(calculator.divide 2).to eq 0.0
expect(calculator.divide 2.0).to eq 0.0
end
it "will make result zero on cancel" do
calculator = Calculator.new
calculator.cancel
expect(calculator.result).to eq 0.0
end
end
Have i structured the code wrongly or my test are wrong? The code works fine but not the test cases.
Well the prime culprit for the infinite loop, is the one you've introduced yourself, in CommandProcessor#parser.
I believe the problem is that you are stubbing gets on your object, but actually, it is defined on Kernel, so if you are going to stub it anywhere, that's where. This means that gets is probably returning some sort of nonsense that your parser does not understand, which leads to the infinite loop of invalid op that you see. It would probably be useful to print what the invalid op was in that case (i.e. change the puts "invalid op" to puts "invalid op: #{operation}".)
It is worth noting that even if your stub had worked, and you kept getting add 3 instead of garbage data, your programming would still loop forever, because the parser never receives an exit, which it needs to break from the loop.
In my opinion, this issue is indicative of another problem: You are trying to test two things in one go: The parser, and the IO. I would suggest altering your interface so that your CommandProcessor.parse took a string, parses it, and returns the result from the internal Calculator instance. Then it becomes almost trivial to test, which is what we want (nobody wants to think too hard).

call next on ruby loop from external method

in Ruby it's easy to tell loop to go to next item
(1..10).each do |a|
next if a.even?
puts a
end
result =>
1
3
5
7
9
but what if I need to call next from outside of the loop (e.g.: method)
def my_complex_method(item)
next if item.even? # this will obviously fail
end
(1..10).each do |a|
my_complex_method(a)
puts a
end
only solution I found and works is to use throw & catch like in SO question How to break outer cycle in Ruby?
def my_complex_method(item)
throw(:skip) if item.even?
end
(1..10).each do |a|
catch(:skip) do
my_complex_method(a)
puts a
end
end
My question is: anyone got any more niftier solution to do this ?? or is throw/catch only way to do this ??
Also what If I want to call my_complex_method not only as a part of that loop (=> don't throw :skip) , can I somehow tell my method it's called from a loop ?
You complex method could return a boolean, and then you compare on your loop like this:
def my_complex_method(item)
true if item.even?
end
(1..10).each do |a|
next if my_complex_method(a)
puts a
end
A simple approach, but different from the try catch one.
UPDATE
As item.even? already return a boolean value, you don't need the true if item.even? part, you can do as follow:
def my_complex_method(item)
item.even?
end
Enumerator#next and Enumerator#peek will be good option to goo :
def my_complex_method(e)
return if e.peek.even?
p e.peek
end
enum = (1..5).each
enum.size.times do |a|
my_complex_method(enum)
enum.next
end
Output
1
3
5
If all you need is to take actions on only some of values, based on value returned by my_complex_method you could use enumerators wisely:
(1..10).map { |a| [a, my_complex_method(a)] }.each do |a, success|
puts a if success
end
You could define method accepting block and take some action in this block based on success or failure there:
(1..10).each do |a|
my_complex_method { |success| next if success }
end
Thanks to scoping, you are able not to use `catch`/`throw`, and call `next` based on processing status.

Function calls in hash come up empty in Ruby

I've been sifting through the prior questions and answers on stackoverflow, and I have gotten most of my question figured out. I figured out that I can't place a function call within a hash, without placing it within a proc, or a similar container.
What I'm ultimately trying to do is have a menu displayed, grab user input, and then iterate through the hash, and run the specified function:
def Main()
menu_titles = {"Answer1" => Proc.new{Choice1()}}
Menu(menu_titles)
end
def Choice1()
puts "Response answer"
end
def Menu(menu_titles)
menu_titles.each_with_index do |(key, value),index|
puts "#{index+1}. #{key}"
end
user_input = 0
menu_titles.each_with_index do |(key, value), index|
if index.eql?(user_input)
menu_titles[value]
break
end
end
end
Main()
The issue I'm having right now is that I'm not entering the functions that my hash calls for. Whether I use a return or a "puts", I either get a blank line or nothing at all. If anyone has other recommendations about my code, I'm all ears also. To be honest, I don't like using procs, but that's mostly because I don't entirely know how they work and where to use them.
Right now for my menus I have:
user_input = 1
if user_input == 1
Choice1()
...
end
Here's how I would refactor this:
class Menu
attr_reader :titles
# initialize sets up a hard-coded titles instance variable,
# but it could easily take an argument.
def initialize
#titles = {
"Answer1" => Proc.new{ puts "choice 1" },
"Answer2" => Proc.new{ puts "choice 2" }
}
end
# This is the only public instance method in your class,
# which should give some idea about what the class is for
# to whoever reads your code
def choose
proc_for_index(display_for_choice)
end
private
# returns the index of the proc.
def display_for_choice
titles.each_with_index { |(key,value), index| puts "#{index + 1}. #{key}" }
gets.chomp.to_i - 1 # gets will return the string value of user input (try it in IRB)
end
# first finds the key for the selected index, then
# performs the hash lookup.
def proc_for_index(index)
titles[titles.keys[index]]
end
end
If you're serious about Ruby (or object-oriented programming in general), I would highly recommend learning about the advantages of packaging your code into behavior-specific classes. This example allows you to do this:
menu = Menu.new
proc = menu.choose
#=> 1. Answer1
#=> 2. Answer2
2 #(user input)
proc.call
#=> choice 2
And you could actually run it on one line:
Menu.new.choose.call

Refactoring respond_to? call in if-elsif-else condition

I have the following method and want to make it more readable:
def value_format(value)
if value.respond_to? :to_actor
value.to_actor
elsif value.respond_to? :to_subject
value.to_subject
elsif value.respond_to? :to_json
value.to_json
elsif value.respond_to? :to_hash
value.to_hash
else
value.inspect
end
end
This is my solution. What do you think?
def value_format(value)
methods = [:to_actor, :to_subject, :to_json, :to_hash, :inspect]
value.send(methods.find_all { |m| m if value.respond_to? m }.first)
end
Your solution looks fine, but you might as well use find instead of find_all:
METHODS = [:to_actor, :to_subject, :to_json, :to_hash, :inspect]
def value_format(value)
value.send(METHODS.find { |m| value.respond_to? m })
end
Using a constant has the advantage of not creating a new array every time value_format is ran.
Seems there's a pretty simple optimization to your solution:
def value_format(value)
methods = [:to_actor, :to_subject, :to_json, :to_hash]
value.send(methods.find(:inspect) { |m| value.respond_to? m })
end
The facets gem provides an elegant solution (I think) to this problem. It combines the two steps of checking if an object responds to a method and actually calling that method into a single step.
So your example could be rewritten as this:
require 'facets/kernel/respond'
def value_format(v)
v.respond.to_actor || v.respond.to_subject || v.respond.to_json || v.respond.to_hash || v.respond.inspect
end
Note that this method only works if it is safe to assume that none of these methods are going to return nil or false (because respond returns nil if the object doesn't respond, that is what allows us to chain it together with a bunch of ors).
Since all of the methods you listed should return strings, I believe this approach would work fine in your example.
Documentation:
# Like #respond_to? but returns the result of the call
# if it does indeed respond.
#
# class RespondExample
# def f; "f"; end
# end
#
# x = RespondExample.new
# x.respond(:f) #=> "f"
# x.respond(:g) #=> nil
#
# or
#
# x.respond.f #=> "f"
# x.respond.g #=> nil

Resources