Get local variables of previous scope - ruby

I am making a Ruby REPL to be used inside an application. I made code:
a = 1
b = 2
currentScope = []
Kernel.local_variables.each do |var|
currentScope << [var,Kernel.eval(var.to_s)]
end
launchREPL(currentScope)
Inside the REPL, I can execute the following code:
#a #=>1
#a+#b #=>3
Ideally I wouldn't have to write the four lines of code before I launch the REPL, and instead I would like to run them inside the launchREPL function. However this would require access to the previous scope from inside the launchREPL function.
Test1
Most notably I tried:
launchREPL(Kernel)
When I do the following:
def launchREPL(scope)
F = 0
puts scope.local_variables # => [:F]
end
it is apparent that this method is not valid.
Test2
launchREPL(Kernel.binding)
def launchREPL(scope)
Kernel.binding.local_variables #= Error: private method 'local_variables' called for #<Binding>
end
Is there any way to do what I'm trying to do?
Edit: P.S. This is currently the code inside launchREPL:
def launchREPL(scope=nil,winName="Ruby REPL")
# ICM RB file Begin:
puts "\"Starting REPL...\""
__b = binding #Evaluating in a binding, keeps track of local variables
__s = ""
###############################################################################
# SEND INSTANCE VARIABLES TO REPL
###############################################################################
#
#How to prepare scope
# currentScope = []
# Kernel.local_variables.each do |var|
# currentScope << [var,Kernel.eval(var.to_s)]
# end
# launchREPL(currentScope)
if scope != nil
scope.each do |varDef|
__b.instance_variable_set "##{varDef[0].to_s}" , varDef[1]
__b.eval("##{varDef[0].to_s} = __b.instance_variable_get(:##{varDef[0].to_s})")
end
end
# to get instance variables: __b.instance_variable_get(__b.instance_variables[0])
# or better: __b.instance_variable_get(:#pipe1)
#
###############################################################################
bStartup = true
while bStartup || __s != ""
# If startup required skip evaluation step
if !bStartup
#Evaluate command
begin
__ret = __s + "\n>" + __b.eval(__s).to_s
rescue
__ret = __s + "\n> Error: " + $!.to_s
end
puts __ret
else
#REPL is already running
bStartup = false
end
#Read user input & print previous output
__s = WSApplication.input_box(__ret,winName,"")
__s == nil ? __s = "" : nil
end
end

Although what you are trying to achieve is unclear and there are definitely many ways to do it properly, every ruby method might be called with Object#send approach:
def launchREPL(scope)
scope.send :local_variables #⇒ here you go
end
a = 42
launchREPL(binding).include?(:a)
#⇒ true
Sidenote: this is how your “4 lines” are usually written in ruby:
local_variables.map { |var| [var, eval(var.to_s)] }
And this is how they should be written (note Binding#local_variable_get):
local_variables.map { |var| [var, binding.local_variable_get(var)] }
The summing up:
def launchREPL(scope)
vars = scope.send(:local_variables).map do |var|
[var, scope.local_variable_get(var)]
end
# some other code
end
a = 42
launchREPL(binding).to_h[:a]
#⇒ 42

This won’t fit the comment, so I would post it as an answer.
def launchREPL(scope = nil, winName = "Ruby REPL")
puts '"Starting REPL..."'
scope.eval('local_variables').each do |var|
instance_variable_set "##{var}", scope.eval(var.to_s)
end if scope
s = ""
loop do
ret = begin
"#{s}\n> #{eval(s)}"
rescue => e
"#{s}\n> Error: #{e.message}"
end
puts ret
# s = WSApplication.input_box(ret, winName, "")
# break if s.empty?
s = "100 * #a" # remove this line and uncomment 2 above
end
end
a = 42
launchREPL(binding)
This is how your function should be written (I have just make it looking as ruby code.) The above works (currently it has no break at all, but you can see as it’s calculating 4200 infinitely.)

Related

How can we replicate irb behaviour using binding method as below?

Trying to write a program interactively which can take inputs from command line as an expression or attributes like -
irb : 3+2
Should evaluate to => 5
Attribute
irb : abc = 1
=> 1
irb : jkl(or def) = 1
=> 1
irb : abc + def
=> 2
Also the evaluation should take place once user inputs blank line.
My efforts : I created a method attr_accessor which iterates through the array of *secret passed to it, and calls define_method on each attr, creating an instance variable getter and setter for each attribute.
Part of code working :
I made a success in evaluating the expressions and returning string values.
irb : 3+2
Should evaluate to => 5
irb : True
=> True
But still stuck with evaluation of assignment to attributes and unable to dynamically store those values in my interactive irb. Below expected results are not working :
Attribute
irb : abc = 1
=> 1
irb : def = 1
=> 1
irb : abc + def
=> 2
Note - I don't want to use "require 'irb' " or " "require 'pry'". Can this be achieved with simple ruby code ?
My Solution:
class Demo
def self.attr_accessor(*secret)
secret.each do |attr|
define_method(attr) { instance_variable_get("##{attr}") }
define_method("#{attr}=") { |val| instance_variable_set("##{attr}", val) }
end
get_binding
end
def self.method_new(input)
#object = attr_accessor(input)
end
def self.method(secret)
#object = Regexp.new(/\A[\d+\-*\/=. ]+\z/).match(secret.to_s) ? eval(secret) : "Invalid expression"
get_binding
end
def self.simple_method(secret)
#object = secret
get_binding
end
def self.get_binding
binding
end
end
user_input = ''
until user_input == 'q' do
user_input = gets.chomp
if user_input =~ /^.*=.*$/
b2 = Demo.method_new(*user_input)
puts eval('#object', b2)
elsif user_input =~ /\A[\d+\-*\/=. ]+\z/
b3 = Demo.method(user_input)
puts eval('#object', b3)
else
b4 = Demo.simple_method(user_input)
puts eval('#object', b4)
end
end
Expected Result:
irb : 3+2
#note - each result evaluated after user enters blank line
Should evaluate to => 5
Attributes ---
irb : abc = 1
#note - each result evaluated after user enters blank line
=> 1
irb : def = 1
#note - each result evaluated after user enters blank line
=> 1
irb : abc + def( or jkl)
#note - each result evaluated after user enters blank line
=> 2
Actual Result : Output is "Invalid expression" for all other inputs except expressions and simple strings.
I believe, I have partly reached to the solution of above problem. Now I can store the values of attributes in a hash map. I tried accessing these values through keys and thus can easily store and display values for assignments like:
rb : x = 1
=> 1
or
rb : y = 1
But the part of code I have written for evaluating 'x + y' is trying to partition it on operator and then accessing value of each attribute.
I am doing something wrong in line of code marked with comment #faulty. Due to which I got output like
=> x y
I am unable to access key values after partitioning.
Can someone please advise on this piece of code alone ?
Solution:
class Module
def initialize(args)
args.each do |key, value|
# the instance_variable_set method dynamically creates instance variables
# with the key as the name and value as the assigned value
instance_variable_set("##{key}",value)
# define_singleton_method creates a getter method with the same name as the
# key and inside the block you define what it returns
define_singleton_method(key){ value }
#defining the setter method
define_singleton_method("#{key}=") do |val|
instance_variable_set("##{key}", val)
end
end
end
end
class Demo
#var :bar
def self.eval_binary_expr(expr)
if expr =~ /^.*=.*$/
obj = Module.new(:name => expr)
#object1 = eval(obj.name)
get_binding
else
obj = Module.new(:name => expr)
l_operand, op, r_operand = (obj.name).partition(%r{[/*+-]}) #Faulty
if op.empty?
raise ArgumentError, "Invalid operation or no operation in expression: #{expr}"
end
case op
when '/'; then #object1 = (l_operand / r_operand); get_binding
when '*'; then #object1 = (l_operand * r_operand); get_binding
when '+'; then #object1 = (l_operand + r_operand); get_binding
when '-'; then #object1 = (l_operand - r_operand); get_binding
end
end
end
def self.method(secret)
#object2 = Regexp.new(/\A[\d+\-*\/=. ]+\z/).match(secret.to_s) ? eval(secret) : "Invalid expression"
get_binding
end
def self.new_method(secret)
#object3 = secret
get_binding
end
def self.get_binding
binding
end
end
user_input = ''
until user_input == 'q' do
user_input = gets.chomp
if user_input =~ /\A[\w+\-*\/=. ]+\z/
b2 = Demo.eval_binary_expr(user_input)
puts eval('#object1', b2)
elsif user_input =~ /\A[\d+\-*\/=. ]+\z/
b3 = Demo.method(user_input)
puts eval('#object2', b3)
else
b4 = Demo.new_method(user_input)
puts eval('#object3', b4)
end
end

Need help indenting tags in the output in Ruby

UPDATE: OK, so I implemented your code, but now the indentation is not showing up! Any ideas what might be wrong? I modified the code so that it would attempt to pass my original test (this is only an exercise so in real life I would not be overriding the XmlDocument class) and here is the modified code:
class XmlDocument
attr_reader :indent_depth, :bool
def initialize(bool = false, indent_depth = 0)
#indent_depth = indent_depth
#bool = bool
end
def method_missing(name, *args)
indentation = ' '*indent_depth
attrs = (args[0] || {}).map { |k, v| " #{k}='#{v}'" }.join(' ')
if block_given?
puts indent_depth
opening = "#{indentation}<#{name}#{attrs}>"
contents = yield(XmlDocument.new(true,indent_depth+1))
closing = "#{indentation}</#{name}>"
bool ? opening + "\n" + contents + "\n" + closing : opening + contents + closing
else
"#{indentation}<#{name}#{attrs}/>"
end
end
end
I'm trying to get the method to pass this test:
it "indents" do
#xml = XmlDocument.new(true)
#xml.hello do
#xml.goodbye do
#xml.come_back do
#xml.ok_fine(:be => "that_way")
end
end
end.should ==
"<hello>\n" +
" <goodbye>\n" +
" <come_back>\n" +
" <ok_fine be='that_way'/>\n" +
" </come_back>\n" +
" </goodbye>\n" +
"</hello>\n"
...but I'm unsure as to where to go with my code, below. I was thinking of using a counter to keep track of how far indented we have to go. I tried some code, but then deleted it because it was getting too messy and I have a feeling that the indentation should not be too complicated to implement.
class XmlDocument
def initialize(bool = false)
#bool = bool
end
def send(tag_name)
"<#{tag_name}/>"
end
def method_missing(meth, arg={}, &block)
arbitrary_method = meth.to_s
tag_string = ''
# 1) test for block
# 2) test for arguments
# 3) test for hash
if block_given? # check for #xml.hello do; #xml.goodbye; end
if yield.class == String # base case: #xml.hello do; "yellow"; end
"<#{arbitrary_method}>#{yield}</#{arbitrary_method}>"
else # in the block we do not have a string, we may have another method
method_missing(yield)
end
elsif arg.empty? # no arguments e.g. #xml.hello
send(arbitrary_method)
else # hash as argument e.g. #xml.hello(:name => 'dolly')
send("#{arbitrary_method} #{arg.keys[0]}='#{arg.values[0]}'")
end
end
end
Your code needs a lot of work - some pointers:
Do not override the send method!
Don't call yield over and over - you don't know what side effects you might cause, not to mention a performance hit - call it once, and remember the return value.
You might want to read up on how to write a DSL (here is a blogpost on the subject), to see how it was done correctly in other places.
Ignoring the above, I will try to answer your question regarding indentation.
In a DSL use case, you might want to use a context object which holds the indentation depth as state:
class Indented
attr_reader :indent_depth
def initialize(indent_depth = 0)
#indent_depth = indent_depth
end
def method_missing(name, *args)
indentation = ' ' * indent_depth
attrs = (args[0] || {}).map { |k, v| "#{k}='#{v}'" }.join(' ')
if block_given?
"#{indentation}<#{name} #{attrs}>\n" +
yield(Indented.new(indent_depth + 1)) +
"\n#{indentation}</#{name}>"
else
"#{indentation}<#{name} #{attrs}/>"
end
end
end
xml = Indented.new
puts xml.hello do |x|
x.goodbye do |x|
x.come_back do |x|
x.ok_fine(:be => "that_way")
end
end
end
# => <hello >
# => <goodbye >
# => <come_back >
# => <ok_fine be='that_way'/>
# => </come_back>
# => </goodbye>
# => </hello>

Ruby: Using StringScanner causes infinite loop

I have the following class:
require 'strscan'
class ConfParser
include Enumerable
class Error < StandardError; end
VERSION = '0.0.1'
SECTION_REGEX = /^\[ # Opening bracket
([^\]]+) # Section name
\]$ # Closing bracket
/x
PARAMETER_REGEX = /^\s*([^:]+) # Option
:
(.*?)$ # Value
/x
attr_accessor :filename, :sections
CONFIG_DIRECTORY = "./config"
ENCODING = "UTF-8"
def self.read(filename, opts = {})
new(opts.merge(:filename => filename))
end
def initialize(opts = {})
#filename = opts.fetch(:filename)
#separator = opts.fetch(:separator, ":")
#file = "#{CONFIG_DIRECTORY}/#{#filename}"
#content = nil
#config = Hash.new { |h,k| h[k] = Hash.new }
load
end
def load
raise_error("First line of config file contain be blank") if first_line_empty?
f = File.open(#file, 'r')
#content = f.read
parse!
ensure
f.close if f && !f.closed?
end
def sections
#config.keys
end
def [](section)
return nil if section.nil?
#config[section.to_s]
end
def []=( section, value )
#config[section.to_s] = value
end
private
def parse!
#_section = nil
#_current_line = nil
property = ''
string = ''
#config.clear
scanner = StringScanner.new(#content)
until scanner.eos?
#_current_line = scanner.check(%r/\A.*$/) if scanner.bol?
if scanner.scan(SECTION_REGEX)
#_section = #config[scanner[1]]
else
tmp = scanner.scan_until(%r/([\n"#{#param}#{#comment}] | \z | \\[\[\]#{#param}#{#comment}"])/mx)
raise_error if tmp.nil?
len = scanner[1].length
tmp.slice!(tmp.length - len, len)
scanner.pos = scanner.pos - len
string << tmp
end
end
process_property(property, string)
logger #config
end
def process_property( property, value )
value.chomp!
return if property.empty? and value.empty?
return if value.sub!(%r/\\\s*\z/, '')
property.strip!
value.strip!
parse_error if property.empty?
current_section[property.dup] = unescape_value(value.dup)
property.slice!(0, property.length)
value.slice!(0, value.length)
nil
end
def logger log
puts "*"*50
puts log
puts "*"*50
end
def first_line_empty?
File.readlines(#file).first.chomp.empty?
end
def raise_error(msg = 'Error processing line')
raise Error, "#{msg}: #{#_current_line}"
end
def current_section
#_section ||= #config['header']
end
end
The above class parses files that are setup like so:
[header]
project: Hello World
budget : 4.5
accessed :205
[meta data]
description : This is a tediously long description of the Hello World
project that you are taking. Tedious isn't the right word, but
it's the first word that comes to mind.
correction text: I meant 'moderately,' not 'tediously,' above.
[ trailer ]
budget:all out of budget.
You start running it like this:
require 'conf_parser'
cf = ConfParser.read "/path/to/conf/file"
For some reason when the parse! method runs, an infinite loop occurs and I can't figure out why. Any reason why this would be happening? I have never used StringScanner before, so it may be my lack of knowledge of the class
At the risk of stating the obvious, you are most likely never satisfying scanner.eos?, which in turn would mean that you're not advancing the scan pointer to the end of the string. Since the only change to scanner.pos in the else branch of parse! is to decrement it (i.e. by len), this would be understandable. If the if branch doesn't advance it to the end, you'll never terminate.

How to `break` a outside loop in closure (Proc, lambda)?

loop { break } can work fine, but
block = Proc.new { break }
# or
# block = lambda { break }
loop(&block) # => LocalJumpError: break from proc-closure
Is it possible to break in a block variable ?
Update:
A example to explain more:
def odd_loop
i = 1
loop do
yield i
i += 2
end
end
def even_loop
i = 2
loop do
yield i
i += 2
end
end
# This work
odd_loop do |i|
puts i
break if i > 10
end
# This doesn't work
break_greater_10 = Proc.new do |i|
puts i
break if i > 10
end
odd_loop(&break_greater_10) # break from proc-closure (LocalJumpError)
even_loop(&break_greater_10) # break from proc-closure (LocalJumpError)
As my comprehension, Proc.new should work same as block (it can return a function from block), but I don't understand why can't break a loop.
P.S. Sorry for my bad english >~<
To solve this problem you could
raise StopIteration
this worked for me.
To return from a block you can use the next keyword.
def foo
f = Proc.new {next ; p 1}
f.call
return 'hello'
end
puts foo # => 'hello' , without 1

ruby syntactic sugar for core only arg parsing

Syntactic sugar for counting the non-switch args?
argn = 0
ARGV.each do |arg|
mission = Mission_DB.new unless mission
if(arg.match(/^\-.*=/)) then
fsav = arg.split('=')[1] if arg.match(/\-save=/)
skip = arg.split('=')[1].split(',') if arg.match(/\-skip=/)
mission.mission = arg.split('=')[1].downcase if arg.match(/\-mission=/)
else
argn += 1
mission.parseP1SCLRV(arg) if argn == 1
template = arg if argn == 2
mission.parseP1SCLRVPLV(arg) if argn == 3
mission.parseDbUserLimits(arg) if argn == 4
end
end
I've tried to find a reusable, clean core only arg parsing syntax that is intuitive (readable without comments). This isn't bad ... but its not best good enough.
I hate the argn crap
I hate the if /-.*=/ followed by each individual switch
each_with_index won't really accomplish what I'm looking for as switches could be embedded anywhere within the non switched args
Feel free to revamp entirely provided we are sufficing the "clean, core only, intuitive" requirements.
Thanks and Cheers --Reed
Have you considered using optparse? It comes with the Ruby standard library.
Here's a class that uses optparse to do option parsing for the arguments in your example. It handles errors, prints its usage if "-h" or "--help" is given, and encapsulates the parsed arguments into a nice class you can pass around:
require 'optparse'
class Arguments
POSITIONAL_ARGS = %w(p1sclrv template p1sclrvplv db_user_limits)
attr_reader :mission
attr_reader :save_path
attr_reader :what_to_skip
attr_reader *POSITIONAL_ARGS
def initialize(argv)
option_parser.parse!(argv)
POSITIONAL_ARGS.each do |positional_arg|
value = argv.shift
instance_variable_set("##{positional_arg}", value)
raise OptionParser::MissingArgument, positional_arg.to_s unless value
end
raise OptionParser::NeedlessArgument, argv.first unless argv.empty?
rescue OptionParser::ParseError => e
puts e
puts option_parser
exit(1)
end
private
def option_parser
OptionParser.new do |op|
op.banner += ' ' + POSITIONAL_ARGS.join(' ')
op.on('--save=PATH', 'Save to PATH') do |value|
#save_path = value
end
op.on('--skip=WHAT', 'Skip WHAT') do |value|
#what_to_skip = value
end
op.on('--mission=NAME', 'Do mission NAME') do |value|
#mission = value
end
end
end
end
In real use, pass ARGV to it:
args = Arguments.new(ARGV)
Here's an example passing a made-up ARGV and printing the parsed arguments:
args = Arguments.new(%w(--skip=FOO alpha bravo charley delta))
p args.p1sclrv # => "alpha"
p args.template # => "bravo"
p args.p1sclrvplv # => "charley"
p args.db_user_limits # => "delta"
p args.mission # => nil
p args.save_path # => nil
p args.what_to_skip # => "FOO"
Here's what the help looks like:
Arguments.new(%w(--help))
# => Usage: foo [options] p1sclrv template p1sclrvplv db_user_limits
# => --save=PATH Save to PATH
# => --skip=WHAT Skip WHAT
# => --mission=NAME Do mission NAME
My own iterating:
fsav = nil
skip = []
template = p1sclrv = p1sclrvplv = dbuserlimits = nil
ARGV.each do |arg|
case arg
when /\-save=/ then fsav = arg.split('=')[1]
when /\-skip=/ then skip = arg.split('=')[1].split(',')
when /\-mission=/ then mission.mission = arg.split('=')[1].downcase
else
unless(p1sclrv) then p1sclrv = arg; next; end
unless(template) then template = arg; next; end
unless(p1sclrvplv) then p1sclrvplv = arg; next; end
unless(dbuserlimits) then dbuserlimits = arg; next; end
end
end
EDIT: Getting better.

Resources