Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Vertical Assignment in Ruby

B5c79b428fca86c70fd59d1c27a980d8?s=47 Kevin Kuchta
November 13, 2021

Vertical Assignment in Ruby

Ruby's long had leftward assignment (x = 3) and recently got rightward assignment (3 => x). The problem here is obvious: we need a vertical assignment operator. In a complete abdication of good taste and common sense, this talk will walk through correcting this heinous oversight. We'll abuse otherwise-respectable metaprogramming tools like Ripper and TracePoint to add a fully functional vequals operator. While this talk is almost 100% bad-ideas-by-volume, you'll learn a few tricks that you can use in your day-to-day work (or at least to terrify your co-workers).

B5c79b428fca86c70fd59d1c27a980d8?s=128

Kevin Kuchta

November 13, 2021
Tweet

Transcript

  1. @kkuchta ruby = "amazing"

  2. @kkuchta ruby = "amazing"

  3. @kkuchta Ruby Assignment x = 3

  4. @kkuchta Ruby Assignment x = 3

  5. @kkuchta Ruby Assignment 3 => x

  6. @kkuchta Ruby Assignment 3 => x

  7. @kkuchta Ruby Assignment 3 = x

  8. Vertical Assignment In Ruby Kevin Kuchta He/Him Daybreak Health @kkuchta

  9. @kkuchta Hello Operator x = 3 3 => x

  10. @kkuchta Hello Operator 3 ? x x = 3 3

    => x
  11. @kkuchta 3 || x Hello Operator x = 3 3

    => x
  12. @kkuchta Hello Operator x = true || false

  13. @kkuchta Hello Operator class Integer; def +(_); 3; end; end

    puts 1 + 1 # returns 3
  14. @kkuchta Hello Operator 🌮

  15. @kkuchta Hello Operator def 🌮🌮🌮 return "yum" end 🌮🌮🌮 =>

    👄 puts 👄 # "yum"
  16. @kkuchta Hello Operator

  17. @kkuchta Vertical Assignment Operator 3 ‖ x

  18. @kkuchta Vertical Assignment Operator Seems like a valid expression... 3

    ‖ x
  19. @kkuchta Vertical Assignment Operator NameError: unde f ined local variable

    or method `‖' for main:Object Seems like a valid expression... 3 ‖ x
  20. @kkuchta Vertical Assignment Operator Object.define_method(:‖) {|*_|} 3 ‖ x

  21. @kkuchta Vequals Operator Object.define_method(:‖) {|*_|} 3 ‖ x

  22. @kkuchta NameError: unde f ined local variable or method `x'

    for main:Object Vequals Operator Object.define_method(:‖) {|*_|} 3 ‖ x
  23. @kkuchta Ruby Assignment x = 3

  24. @kkuchta Ruby Assignment 3 => x

  25. @kkuchta Ruby Assignment Object.define_method(:‖) {|*_|} 3 ‖ x

  26. @kkuchta TracePoint

  27. @kkuchta TracePoint tracepoint = TracePoint.new(:raise) do |tp| # Do cool

    thing here end tracepoint.enable
  28. @kkuchta TracePoint tracepoint = TracePoint.new(:raise) do |tp| # Do cool

    thing here end tracepoint.enable raise "zomg" raise "wtf" raise "bbq"
  29. @kkuchta tracepoint = TracePoint.new(:call) do |tp| # Do cool thing

    here end tracepoint.enable TracePoint
  30. @kkuchta tracepoint = TracePoint.new(:call) do |tp| # Do cool thing

    here end tracepoint.enable TracePoint 123.to_i foo.bar() [1,2,3].last
  31. @kkuchta TracePoint on every line # run this block on

    every subsequent line... tracepoint = TracePoint.new(:line) do |tp| puts "tracing line #{tp.lineno} in file #{tp.path}" end # ...starting now! tracepoint.enable
  32. @kkuchta TracePoint on every line # run this block on

    every subsequent line... tracepoint = TracePoint.new(:line) do |tp| puts "tracing line #{tp.lineno} in file #{tp.path}" end # ...starting now! tracepoint.enable x = 3 # prints "tracing line 6 in file ./tmp.rb" y = x + 1 # prints "tracing line 7 in file ./tmp.rb" z = x / y # prints "tracing line 9 in file ./tmp.rb"
  33. @kkuchta Binding tracepoint = TracePoint.new(:call) do |tp| tp.binding end tracepoint.enable

  34. @kkuchta Binding binding_at_this_point = binding()

  35. @kkuchta Binding def foo x = 3 return binding() end

  36. @kkuchta Binding def foo x = 3 return binding() end

    puts x # error: no local variable x
  37. @kkuchta Binding def foo x = 3 return binding() end

    puts x # error: no local variable x foos_binding = foo
  38. @kkuchta Binding def foo x = 3 return binding() end

    puts x # error: no local variable x foos_binding = foo puts foos_binding.local_variable_get(:x) # 3
  39. @kkuchta TracePoint 3 ‖ x

  40. @kkuchta TracePoint 3 ‖ x

  41. @kkuchta Binding TracePoint.new(:line) do |tp| line = tp.line if line.include?

    "x" tp.binding.local_variable_set(:x, "🐥") end end.enable puts x
  42. @kkuchta Binding TracePoint.new(:line) do |tp| line = tp.line if line.include?

    "x" tp.binding.local_variable_set(:x, "🐥") end end.enable puts x
  43. @kkuchta TracePoint

  44. @kkuchta TracePoint tp.lineno # 123

  45. @kkuchta TracePoint tp.lineno # 123 tp.path # ./whatever.rb

  46. @kkuchta TracePoint tp.lineno # 123 tp.path # ./whatever.rb scruples

  47. @kkuchta TracePoint TracePoint.new(:line) do |tp| line = File.readlines(tp.path)[tp.lineno - 1]

    puts line; end.enable # prints out "x = 4": x = 4
  48. @kkuchta TracePoint.new(:line) do |tp| line = File.readlines(tp.path)[tp.lineno - 1] if

    line.include? "x" tp.binding.local_variable_set(:x, "🐥") end end.enable puts x TracePoint.new(:line) do |tp| line = File.readlines(tp.path)[tp.lineno - 1] if line.include? "x" tp.binding.local_variable_set(:x, "🐥") end end.enable puts x Binding
  49. @kkuchta TracePoint.new(:line) do |tp| line = File.readlines(tp.path)[tp.lineno - 1] if

    line.include? "x" tp.binding.local_variable_set(:x, "🐥") end end.enable puts x Binding
  50. @kkuchta TracePoint.new(:line) do |tp| line = File.readlines(tp.path)[tp.lineno - 1] if

    line.include? "x" tp.binding.local_variable_set(:x, "🐥") end end.enable puts x Binding
  51. @kkuchta Binding x = 3 my_binding = binding() my_binding.local_variable_set(:x, 4)

    puts x # 4
  52. @kkuchta Binding x = 3 my_binding = binding() my_binding.local_variable_set(:x, 4)

    puts x # 4 my_binding.local_variable_set(:y, 5) puts y # error, y is undefined!
  53. @kkuchta Binding

  54. @kkuchta Binding my_binding = binding() my_binding.eval("def x(*args) = 3") puts

    x # 3
  55. @kkuchta Binding my_binding = binding() my_binding.eval("def x(*args) = 3") puts

    x # 3 x = 4 puts x # 4
  56. @kkuchta Binding line = tp.line line = File.readlines(tp.path)[tp.lineno - 1]

    tp.binding.local_variable_set(:x, "3") my_binding.eval("def x(*args) = 3")
  57. @kkuchta Success! TracePoint.new(:line) do |tp| line = File.readlines(tp.path)[tp.lineno - 1]

    if line.include?("x") tp.binding.eval("def x(*args) = 3") end end.enable puts x # 3 TracePoint.new(:line) do |tp| line = File.readlines(tp.path)[tp.lineno - 1] if line.include?("x") tp.binding.eval("def x(*args) = 3") end end.enable puts x # 3
  58. @kkuchta Success! TracePoint.new(:line) do |tp| line = File.readlines(tp.path)[tp.lineno - 1]

    if line.include?("x") tp.binding.eval("def x(*args) = 3") end end.enable puts x # 3
  59. @kkuchta Success! TracePoint.new(:line) do |tp| line = File.readlines(tp.path)[tp.lineno - 1]

    line.split(' ').each do |token| next if token == "puts" tp.binding.eval("def #{token}(*args) = 3") end end.enable puts x puts y puts z TracePoint.new(:line) do |tp| line = File.readlines(tp.path)[tp.lineno - 1] line.split(' ').each do |token| next if token == "puts" tp.binding.eval("def #{token}(*args) = 3") end end.enable puts x puts y puts z
  60. @kkuchta Success! TracePoint.new(:line) do |tp| line = File.readlines(tp.path)[tp.lineno - 1]

    line.split(' ').each do |token| next if token == "puts" tp.binding.eval("def #{token}(*args) = 3") end end.enable puts x puts y puts z
  61. @kkuchta Brittle as heck > line = "puts x #

    hi there!" > line.split(' ') ["puts", "x", "#", "hi", "there!"]
  62. @kkuchta Success! x = y * 3 + z(7) #

    foo
  63. @kkuchta Success! Identi f ier Operator Literal Comment x =

    y * 3 + z(7) # foo
  64. @kkuchta Ripper

  65. @kkuchta Ripper require 'ripper' pp Ripper.sexp(" x = 3 y

    = x + 1 ")
  66. @kkuchta Ripper require 'ripper' pp Ripper.sexp(" x = 3 y

    = x + 1 ") [:program, [[:assign, [:var_field, [:@ident, "x", [2, 2]]], [:@int, "3", [2, 6]]], [:assign, [:var_field, [:@ident, "y", [3, 2]]], [:binary, [:var_ref, [:@ident, "x", [3, 6]]], :+, [:@int, "1", [3, 10]]]]]]
  67. @kkuchta Sexp pp Ripper.sexp('def foo;') # nil pp Ripper.sexp('x =

    (') # nil pp Ripper.sexp('class bar') # nil
  68. @kkuchta Sexp "foo = bar + baz()" ["foo", " ",

    "=", " ", "bar", " ", "+", " ", "baz", "(", ")"] [[:assign, [:var_field, [:@ident, "foo", [1, 0]]], [:binary, [:vcall, [:@ident, "bar", [1, 6]]], :+, [:method_add_arg, [:fcall, [:@ident, "baz", [1, 12]]], [:arg_paren, nil]]]]]
  69. @kkuchta Lexing Ripper.lex("foo = bar + baz()") .map{|_,_,token,_| token} =>

    ["foo", " ", "=", " ", "bar", " ", "+", " ", "baz", "(", ")"]
  70. @kkuchta Split by spaces TracePoint.new(:line) do |tp| line = File.readlines(tp.path)[tp.lineno

    - 1] line.split(' ').each do |token| next if token == "puts" tp.binding.eval("def #{token}(*args) = 3") end end.enable puts x
  71. @kkuchta Lexing TracePoint.new(:line) do |tp| line = File.readlines(tp.path)[tp.lineno - 1]

    tokens = Ripper.lex(line) tokens.each do |_, __, token, ___| next if token == "puts" || token == " " tp.binding.eval("def #{token}(*args) = 3") end end.enable puts x TracePoint.new(:line) do |tp| line = File.readlines(tp.path)[tp.lineno - 1] tokens = Ripper.lex(line) tokens.each do |_, __, token, ___| next if token == "puts" || token == " " tp.binding.eval("def #{token}(*args) = 3") end end.enable puts x TracePoint.new(:line) do |tp| line = File.readlines(tp.path)[tp.lineno - 1] tokens = Ripper.lex(line) tokens.each do |_, __, token, ___| next if token == "puts" || token == " " tp.binding.eval("def #{token}(*args) = 3") end end.enable puts x
  72. @kkuchta Lexing TracePoint.new(:line) do |tp| line = File.readlines(tp.path)[tp.lineno - 1]

    tokens = Ripper.lex(line) tokens.each do |_, __, token, ___| next if token == "puts" || token == " " tp.binding.eval("def #{token}(*args) = 3") end end.enable puts x TracePoint.new(:line) do |tp| line = File.readlines(tp.path)[tp.lineno - 1] tokens = Ripper.lex(line) tokens.each do |_, __, token, ___| next if token == "puts" || token == " " tp.binding.eval("def #{token}(*args) = 3") end end.enable puts x
  73. @kkuchta > Ripper.lex("foo = bar + baz()") [ [[1, 0],

    :on_ident, "foo", CMDARG], [[1, 3], :on_sp, " ", CMDARG], [[1, 4], :on_op, "=", BEG], [[1, 5], :on_sp, " ", BEG], [[1, 6], :on_ident, "bar", ARG], [[1, 9], :on_sp, " ", ARG], [[1, 10], :on_op, "+", BEG], [[1, 11], :on_sp, " ", BEG], [[1, 12], :on_ident, "baz", ARG], [[1, 15], :on_lparen, "(", BEG|LABEL], [[1, 16], :on_rparen, ")", ENDFN] ] Lexing into tokens
  74. @kkuchta > Ripper.lex("foo = bar + baz()") [ [[1, 0],

    :on_ident, "foo", CMDARG], [[1, 3], :on_sp, " ", CMDARG], [[1, 4], :on_op, "=", BEG], [[1, 5], :on_sp, " ", BEG], [[1, 6], :on_ident, "bar", ARG], [[1, 9], :on_sp, " ", ARG], [[1, 10], :on_op, "+", BEG], [[1, 11], :on_sp, " ", BEG], [[1, 12], :on_ident, "baz", ARG], [[1, 15], :on_lparen, "(", BEG|LABEL], [[1, 16], :on_rparen, ")", ENDFN] ] Lexing into tokens
  75. @kkuchta > Ripper.lex("foo = bar + baz()") [ [[1, 0],

    :on_ident, "foo", CMDARG], [[1, 3], :on_sp, " ", CMDARG], [[1, 4], :on_op, "=", BEG], [[1, 5], :on_sp, " ", BEG], [[1, 6], :on_ident, "bar", ARG], [[1, 9], :on_sp, " ", ARG], [[1, 10], :on_op, "+", BEG], [[1, 11], :on_sp, " ", BEG], [[1, 12], :on_ident, "baz", ARG], [[1, 15], :on_lparen, "(", BEG|LABEL], [[1, 16], :on_rparen, ")", ENDFN] ] Lexing into tokens
  76. @kkuchta > Ripper.lex("foo = bar + baz()") [ [[1, 0],

    :on_ident, "foo", CMDARG], [[1, 3], :on_sp, " ", CMDARG], [[1, 4], :on_op, "=", BEG], [[1, 5], :on_sp, " ", BEG], [[1, 6], :on_ident, "bar", ARG], [[1, 9], :on_sp, " ", ARG], [[1, 10], :on_op, "+", BEG], [[1, 11], :on_sp, " ", BEG], [[1, 12], :on_ident, "baz", ARG], [[1, 15], :on_lparen, "(", BEG|LABEL], [[1, 16], :on_rparen, ")", ENDFN] ] Lexing into tokens
  77. @kkuchta > Ripper.lex("foo = bar + baz()") [ [[1, 0],

    :on_ident, "foo", CMDARG], [[1, 3], :on_sp, " ", CMDARG], [[1, 4], :on_op, "=", BEG], [[1, 5], :on_sp, " ", BEG], [[1, 6], :on_ident, "bar", ARG], [[1, 9], :on_sp, " ", ARG], [[1, 10], :on_op, "+", BEG], [[1, 11], :on_sp, " ", BEG], [[1, 12], :on_ident, "baz", ARG], [[1, 15], :on_lparen, "(", BEG|LABEL], [[1, 16], :on_rparen, ")", ENDFN] ] Lexing into tokens
  78. @kkuchta > Ripper.lex("foo = bar + baz()") [ [[1, 0],

    :on_ident, "foo", CMDARG], [[1, 3], :on_sp, " ", CMDARG], [[1, 4], :on_op, "=", BEG], [[1, 5], :on_sp, " ", BEG], [[1, 6], :on_ident, "bar", ARG], [[1, 9], :on_sp, " ", ARG], [[1, 10], :on_op, "+", BEG], [[1, 11], :on_sp, " ", BEG], [[1, 12], :on_ident, "baz", ARG], [[1, 15], :on_lparen, "(", BEG|LABEL], [[1, 16], :on_rparen, ")", ENDFN] ] Lexing into tokens
  79. @kkuchta Searching for identi f iers require 'ripper' TracePoint.new(:line) do

    |tp| line = File.readlines(tp.path)[tp.lineno - 1] tokens = Ripper.lex(line) tokens.each do |_, type, token, ___| if type == :on_ident tp.binding.eval("def #{token}(*args) = 3") end end end.enable puts x
  80. @kkuchta Searching for identi f iers require 'ripper' TracePoint.new(:line) do

    |tp| line = File.readlines(tp.path)[tp.lineno - 1] tokens = Ripper.lex(line) tokens.each do |_, type, token, ___| if type == :on_ident next if tp.binding.receiver.respond_to?(token) || Kernel.respond_to?(token) tp.binding.eval("def #{token}(*args) = 3") end end end.enable puts x # 3 require 'ripper' TracePoint.new(:line) do |tp| line = File.readlines(tp.path)[tp.lineno - 1] tokens = Ripper.lex(line) tokens.each do |_, type, token, ___| if type == :on_ident next if tp.binding.receiver.respond_to?(token) || Kernel.respond_to?(token) tp.binding.eval("def #{token}(*args) = 3") end end end.enable puts x # 3
  81. @kkuchta Searching for identi f iers require 'ripper' TracePoint.new(:line) do

    |tp| line = File.readlines(tp.path)[tp.lineno - 1] tokens = Ripper.lex(line) tokens.each do |_, type, token, ___| if type == :on_ident next if tp.binding.receiver.respond_to?(token) || Kernel.respond_to?(token) tp.binding.eval("def #{token}(*args) = 3") end end end.enable puts x # 3
  82. @kkuchta Putting it together Object.send(:define_method, :‖) {|*_|} TracePoint.new(:line) do |tp|

    # ... see previous slide ... end 7 ‖ x
  83. @kkuchta High Level Approach 7 ‖ foo

  84. @kkuchta High Level Approach 0 1 2 7 ‖ f

    o o Column Line
  85. @kkuchta High Level Approach 0 1 2 7 ‖ f

    o o Column Line
  86. @kkuchta High Level Approach 0 1 2 7 ‖ f

    o o Column Line
  87. @kkuchta High Level Approach 0 1 2 7 ‖ f

    o o Column Line 1. Lex this line and look for ‖
  88. @kkuchta High Level Approach 0 1 2 7 ‖ f

    o o Column Line 1. Lex this line and look for ‖ 2. Lex the line above for expressions
  89. @kkuchta High Level Approach 0 1 2 7 ‖ f

    o o Column Line 1. Lex this line and look for ‖ 2. Lex the line above for expressions
  90. @kkuchta High Level Approach 0 1 2 7 ‖ f

    o o Column Line 1. Lex this line and look for ‖ 2. Lex the line above for expressions 3. Stash the column and expression
  91. @kkuchta High Level Approach 0 1 2 7 ‖ f

    o o Column Line 1. Lex this line and look for ‖ 2. Lex the line above for expressions 3. Stash the column and expression vequals_to_process = [ {col: 1, expression: "7"} ]
  92. @kkuchta High Level Approach 0 1 2 7 ‖ f

    o o Column Line 1. Lex this line and look for ‖ 2. Lex the line above for expressions 3. Stash the column and expression 4. Store the value of the expression vequals_to_process = [ {col: 1, expression: "7"} ]
  93. @kkuchta High Level Approach 0 1 2 7 ‖ f

    o o Column Line 1. Lex this line and look for ‖ 2. Lex the line above for expressions 3. Stash the column and expression 4. Store the value of the expression vequals_to_process = [ {col: 1, value: eval("7")} ]
  94. @kkuchta High Level Approach 0 1 2 7 ‖ f

    o o Column Line 1. Lex this line and look for ‖ 2. Lex the line above for expressions 3. Stash the column and expression 4. Store the value of the expression vequals_to_process = [ {col: 1, value: 7} ]
  95. @kkuchta High Level Approach 0 1 2 7 ‖ f

    o o Column Line vequals_to_process = [ {col: 1, value: 7} ]
  96. @kkuchta High Level Approach 0 1 2 7 ‖ f

    o o Column Line vequals_to_process = [ {col: 1, value: 7} ]
  97. @kkuchta High Level Approach 0 1 2 7 ‖ f

    o o Column Line 1.Lex this line for identi f iers vequals_to_process = [ {col: 1, value: 7} ]
  98. @kkuchta High Level Approach 0 1 2 7 ‖ f

    o o Column Line 1.Lex this line for identi f iers Identi f ier 'foo' spans from 0 to 2 vequals_to_process = [ {col: 1, value: 7} ]
  99. @kkuchta High Level Approach 0 1 2 7 ‖ f

    o o Column Line 1.Lex this line for identi f iers 2.Is this identi f ier is below a ‖? Identi f ier 'foo' spans from 0 to 2 vequals_to_process = [ {col: 1, value: 7} ]
  100. @kkuchta High Level Approach 0 1 2 7 ‖ f

    o o Column Line 1.Lex this line for identi f iers 2.Is this identi f ier is below a ‖? 3.Just-in-time de f ine that identi f ier to equal that value Identi f ier 'foo' spans from 0 to 2 vequals_to_process = [ {col: 1, value: 7} ]
  101. @kkuchta High Level Approach 0 1 2 7 ‖ f

    o o Column Line 1.Lex this line for identi f iers 2.Is this identi f ier is below a ‖? 3.Just-in-time de f ine that identi f ier to equal that value Identi f ier 'foo' spans from 0 to 2 vequals_to_process = [ {col: 1, value: 7} ] def #{identifier}(*args) = value
  102. @kkuchta High Level Approach 0 1 2 7 ‖ f

    o o Column Line 1.Lex this line for identi f iers 2.Is this identi f ier is below a ‖? 3.Just-in-time de f ine that identi f ier to equal that value Identi f ier 'foo' spans from 0 to 2 vequals_to_process = [ {col: 1, value: 7} ] def foo(*args) = 7
  103. @kkuchta High Level Approach 1. Look for vequals to process

    next line 2. Look for identi f iers that line up with vequals from the previous line
  104. @kkuchta Low Level Approach require 'ripper' class Vequals def enable

    @vequals_by_line = {} trace = TracePoint.new(:line) do |tp| line = File.readlines(tp.path)[tp.lineno - 1] check_for_vequals(line, tp) process_any_vequals_from_previous_line(line, tp) end Object.define_method(:‖) {|*_|} trace.enable end def self.enable() Vequals.new.enable end def check_for_vequals(line, tp) @vequals_by_line[tp.lineno] = [] column = 0
  105. @kkuchta Low Level Approach def self.enable() Vequals.new.enable end def check_for_vequals(line,

    tp) @vequals_by_line[tp.lineno] = [] column = 0 lexed = Ripper.lex(line) lexed.each do |_positions, type, token, state| if type == :on_ident && token == "‖" exp_above = get_exp(tp.path, tp.lineno - 1, column) eval_result = tp.binding.eval(exp_above) @vequals_by_line[tp.lineno] << { value_to_assign: eval_result, column: column } end column += token.length end end def process_any_vequals_from_previous_line(line, tp) return unless vequals_to_process = @vequals_by_line[tp.lineno-1]
  106. @kkuchta Low Level Approach end column += token.length end end

    def process_any_vequals_from_previous_line(line, tp) return unless vequals_to_process = @vequals_by_line[tp.lineno-1] lexed = Ripper.lex(line) column = 0 lexed.each do |_positions, type, token, state| if type == :on_ident matching_vequals_entry = vequals_to_process.find do |vequals_entry| (column..(column + token.length)).include?(vequals_entry[:column]) end if matching_vequals_entry value_to_assign = matching_vequals_entry[:value_to_assign] if tp.binding.local_variable_defined?(token.to_sym) tp.binding.local_variable_set(token.to_sym, value_to_assign) else value_as_literal = literalize(value_to_assign) assignment_code = "def #{token}(*args) = #{value_as_literal}" tp.binding.eval(assignment_code) end end end column += token.length
  107. @kkuchta Low Level Approach

  108. @kkuchta Low Level Approach require 'ripper' class Vequals def enable

    @vequals_by_line = {} trace = TracePoint.new(:line) do |tp| line = File.readlines(tp.path)[tp.lineno - 1] check_for_vequals(line, tp) process_any_vequals_from_previous_line(line, tp) end Object.define_method(:‖) {|*_|} trace.enable end def self.enable() Vequals.new.enable end def check_for_vequals(line, tp) @vequals_by_line[tp.lineno] = [] column = 0 lexed = Ripper.lex(line) lexed.each do |_positions, type, token, state| if type == :on_ident && token == "‖" exp_above = get_exp(tp.path, tp.lineno - 1, column) eval_result = tp.binding.eval(exp_above) @vequals_by_line[tp.lineno] << { value_to_assign: eval_result, column: column } end column += token.length end end def process_any_vequals_from_previous_line(line, tp) return unless vequals_to_process = @vequals_by_line[tp.lineno-1] lexed = Ripper.lex(line) column = 0 lexed.each do |_positions, type, token, state| if type == :on_ident matching_vequals_entry = vequals_to_process.find do | vequals_entry| (column..(column + token.length)).include? (vequals_entry[:column]) end if matching_vequals_entry value_to_assign = matching_vequals_entry[:value_to_assign] if tp.binding.local_variable_defined?(token.to_sym) tp.binding.local_variable_set(token.to_sym, value_to_assign) else value_as_literal = literalize(value_to_assign) assignment_code = "def #{token}(*args) = #{value_as_literal}" tp.binding.eval(assignment_code) end end end column += token.length end end def get_exp(path, lineno, column) line = File.readlines(path)[lineno - 1] exp_ranges = get_exp_ranges(line) exp_range = exp_ranges.find do |exp_range| exp_range[1].include?(column) end exp_range[0] end def get_exp_ranges(line) lexed = Ripper.lex(line) column = 0 exp_ranges = [] lexed.each do |_positions, type, token, state| case type when :on_tstring_content exp_ranges << ["'" + token + "'", (column..(column + token.length))] when :on_ident exp_ranges << [token, (column..(column + token.length))] when :on_int exp_ranges << [token, (column..(column + token.length))] else # Skipping type end column += token.length end exp_ranges end def literalize(value) if value.is_a? String '"' + value + '"' else value end end end
  109. @kkuchta It Works! Vequals.enable 7 ‖ foo puts foo #

    prints 7!
  110. @kkuchta It Works! Vequals.enable [7, 123] ‖ ‖ foo bar

    puts foo # prints 7! puts bar # prints 123!
  111. @kkuchta It Works! "yay" ‖ 123 foo ‖ bar puts

    foo # "yay" puts bar # 123
  112. @kkuchta It Works! "foo" => bar ‖ aaaaa ‖ ‖

    c = b ddd
  113. @kkuchta It Works! 3 => fooooooobaaaaar ‖ ‖ ‖ ‖

    ‖ d e f g h
  114. @kkuchta It Works! [3, 4, 5] ‖ ‖ ‖ a

    = b <= 6 ‖ ‖ d => c ‖ ‖ x == e ‖ ‖ f = g = h => z
  115. @kkuchta It Works! 1 ‖ x = x = x

    = x = x ‖ ‖ ‖ ‖ ‖ l = x = x = x = x ‖ ‖ ‖ ‖ ‖ l = x = x = x = l ‖ ‖ ‖ ‖ ‖ l = x = x = x = l ‖ ‖ i =___= i ‖ ‖ ‖ ‖ z z z z
  116. @kkuchta Next Up x ⇑ 3

  117. @kkuchta Next Up w => x ⇑ ‖ z =

    y
  118. @kkuchta Exercises for the reader - Upward assignment - Diagonal

    assignment - Inward/outward assignment along the z axis - Forward and backwards in time assignment
  119. @kkuchta Q&A "foo" => bar ‖ aaaaa ‖ ‖ c

    = b ddd
  120. @kkuchta FAQ "foo" => bar ‖ aaaaa ‖ ‖ c

    = b ddd
  121. @kkuchta FAQ Q: Who's responsible for this mess "foo" =>

    bar ‖ aaaaa ‖ ‖ c = b ddd
  122. @kkuchta FAQ Q: Who's responsible for this mess A: Kevin

    Kuchta, @kkuchta, kevinkuchta.com "foo" => bar ‖ aaaaa ‖ ‖ c = b ddd
  123. @kkuchta FAQ Q: Who's responsible for this mess A: Kevin

    Kuchta, @kkuchta, kevinkuchta.com Q: Where can I see this code in more detail? "foo" => bar ‖ aaaaa ‖ ‖ c = b ddd
  124. @kkuchta FAQ Q: Who's responsible for this mess A: Kevin

    Kuchta, @kkuchta, kevinkuchta.com Q: Where can I see this code in more detail? A: github.com/kkuchta/vequals "foo" => bar ‖ aaaaa ‖ ‖ c = b ddd
  125. @kkuchta FAQ Q: Who's responsible for this mess A: Kevin

    Kuchta, @kkuchta, kevinkuchta.com Q: Where can I see this code in more detail? A: github.com/kkuchta/vequals Q: Should I use this in production? "foo" => bar ‖ aaaaa ‖ ‖ c = b ddd
  126. @kkuchta FAQ Q: Who's responsible for this mess A: Kevin

    Kuchta, @kkuchta, kevinkuchta.com Q: Where can I see this code in more detail? A: github.com/kkuchta/vequals Q: Should I use this in production? A: Absolutely. "foo" => bar ‖ aaaaa ‖ ‖ c = b ddd
  127. @kkuchta FAQ Q: Who's responsible for this mess A: Kevin

    Kuchta, @kkuchta, kevinkuchta.com Q: Where can I see this code in more detail? A: github.com/kkuchta/vequals Q: Should I use this in production? A: Absolutely.(Not you, Luke) "foo" => bar ‖ aaaaa ‖ ‖ c = b ddd
  128. @kkuchta FAQ Q: Who's responsible for this mess A: Kevin

    Kuchta, @kkuchta, kevinkuchta.com Q: Where can I see this code in more detail? A: github.com/kkuchta/vequals Q: Should I use this in production? A: Absolutely. Q: I have a real question (Not you, Luke) "foo" => bar ‖ aaaaa ‖ ‖ c = b ddd
  129. @kkuchta FAQ Q: Who's responsible for this mess A: Kevin

    Kuchta, @kkuchta, kevinkuchta.com Q: Where can I see this code in more detail? A: github.com/kkuchta/vequals Q: Should I use this in production? A: Absolutely. Q: I have a real question A: Right here! (Not you, Luke) "foo" => bar ‖ aaaaa ‖ ‖ c = b ddd
  130. @kkuchta FAQ Q: Who's responsible for this mess A: Kevin

    Kuchta, @kkuchta, kevinkuchta.com Q: Where can I see this code in more detail? A: github.com/kkuchta/vequals Q: Should I use this in production? A: Absolutely. Q: I have a real question A: Right here! Q: Who in gods name hired you? (Not you, Luke) "foo" => bar ‖ aaaaa ‖ ‖ c = b ddd
  131. @kkuchta DaybreakHealth.com kevin@daybreakhealth.com https://daybreak-health.breezy.hr/p/a27079cba61a-sr-backend-engineer

  132. @kkuchta Thank You DaybreakHealth.com kevin@daybreakhealth.com https://daybreak-health.breezy.hr/p/a27079cba61a-sr-backend-engineer