Skip to content

Instantly share code, notes, and snippets.

@palkan

palkan/config.rb Secret

Created May 10, 2024 05:19
Show Gist options
  • Save palkan/dc0f649842b7f3514d9de6ca91475001 to your computer and use it in GitHub Desktop.
Save palkan/dc0f649842b7f3514d9de6ca91475001 to your computer and use it in GitHub Desktop.
[ruby-next] double-dispatch
RubyNext.define_text_rewriter "def_in" do
parser do
def default
@method_name = ""
many(
alt(
seq(
seq(opt_ws, string("def ")),
method_name.fmap do
@method_name = _1.join
@method_name
end,
string(" in ").fmap { nil },
method_pattern,
method_def_end.fmap do
@method_end = _1
nil
end,
lazy { method_body(@method_end).join },
lazy { method_end(@method_end).fmap { nil } }
).fmap { [:pattern, _1.compact] },
seq(
seq(opt_ws, string("def ")),
lazy { string(@method_name) },
string("(...)").fmap { :else },
method_def_end.fmap do
@method_end = _1
nil
end,
lazy { method_body(@method_end).join },
lazy { method_end(@method_end).fmap { nil } }
).fmap { [:pattern_else, _1.compact] },
any_char.fmap { [:code, _1] }
)
).fmap { reduce_tokens(_1) }
end
def method_pattern
many(not_followed_by(end_of_method_def).bind { any_char }).join
end
def method_def_end
alt(string(";"), end_of_line, wrap(opt_ws, opt_ws, string("=")))
end
def end_of_method_def
alt(string(";"), end_of_line)
end
def method_name
many(regexp(/[\w_]/))
end
def method_end(def_end)
# endless method
if def_end.include?("=")
lookahead(end_of_line)
else
seq(end_of_method_def, opt_ws, string("end")).join
end
end
def method_body(def_end)
many(
alt(
seq(
string("do"),
lazy { alt(balanced("do", "end", any_char), many(none_of("end"))) },
string("end")
),
seq(
string("begin"),
lazy { alt(balanced("do", "end", any_char), many(none_of("end"))) },
string("end")
),
not_followed_by(method_end(def_end)).bind { any_char }
)
)
end
def reduce_tokens(tokens)
state = nil
parts = []
method_pattern_index = {}
tokens.each do |(type, value)|
if type == :code
if state == :pattern || state.nil?
parts << +""
state = :code
end
if state == :code
parts.last << value
end
end
if type == :pattern || type == :pattern_else
state = :pattern
_, method_name = *value
unless method_pattern_index[method_name]
method_pattern_index[method_name] = parts.size
parts << []
end
parts[method_pattern_index[method_name]] << value
end
end
method_pattern_index.each do |method_name, index|
patterns = parts[index]
indented_def = patterns.first.first.join
new_header = <<~RUBY
#{indented_def}#{method_name}(*args, **hargs) = case [args, hargs]
RUBY
indent = indented_def.sub(/^\n+/, "").match(/^\s*/)[0].size
in_clauses = patterns.map do |_, _, pattern, body|
clause =
if pattern == :else
<<~RUBY
else
RUBY
else
<<~RUBY
in [#{pattern}], _
RUBY
end
indented_body = if body.split("\n").size == 1
indented(body.sub(/^\s*/, ""), indent + 4)
else
indented(body, 2)
end
"#{indented(clause, indent + 2)}#{indented_body}\n"
end
parts[index] = [new_header, *in_clauses, indented("end", indent + 2)].join
end
parts
end
def indented(text, indent)
text.gsub(/^/, " " * indent)
end
end
def safe_rewrite(source)
parse(source).then(&:join)
end
end
# Method overloading (strictly speaking, double dispatch) for Ruby.
# The example is based on the example from the classic https://learnyousomeerlang.com/syntax-in-functions#pattern-matching
def beach in :celcius | :c, (20..45)
:favorable
end
def beach in :kelvin | :k, (293..318)
:scientifically_favorable
end
def beach in :kelvin | :k, (5_778...)
:burning_on_the_sun
end
def beach in :fahrenheit | :f, (68..113)
:favorable_in_us
end
def beach(...) = :avoid_beach
puts beach(:c, 23)
puts beach(:kelvin, 300)
puts beach(:wat)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment