575 lines
9.8 KiB
Ruby
575 lines
9.8 KiB
Ruby
# kitchen_sink.rb — a big Ruby syntax exercise file
|
|
|
|
# --- Constants, globals, class vars, instance vars
|
|
$global_var = :glob
|
|
GLOBAL_CONST = 123
|
|
AnotherConst = { a: 1, b: 2 }
|
|
|
|
# --- Basic literals
|
|
int_lit = 42
|
|
float_lit = 3.1415
|
|
big_int = 123_456_789_012_345_678
|
|
ratio = Rational(2, 3)
|
|
cplx = Complex(2, -5)
|
|
bool_t = true
|
|
bool_f = false
|
|
nil_val = nil
|
|
|
|
|
|
# --- Strings and symbols
|
|
s1 = "double-quoted string with escape\nand interpolation: #{int_lit}"
|
|
s2 = 'single-quoted string with \\n literal'
|
|
s3 = %Q{interpolated %Q string #{1 + 1}}
|
|
s4 = %q{non-interpolated %q string}
|
|
sym1 = :symbol
|
|
sym2 = :"spaced symbol"
|
|
sym3 = %s{another_symbol}
|
|
|
|
# --- Heredocs
|
|
heredoc1 = <<~RUBY
|
|
def sample(x)
|
|
x * 2
|
|
end
|
|
RUBY
|
|
|
|
heredoc2 = <<-'TXT'
|
|
literal heredoc with \n not interpreted
|
|
TXT
|
|
|
|
# --- Regex, ranges
|
|
re = /foo\d+/i
|
|
re2 = %r{^/api/v1/.*$}
|
|
range_inc = 1..5
|
|
range_exc = 1...5
|
|
char_range = 'a'..'f'
|
|
|
|
# --- Arrays, hashes, keywords splats
|
|
arr = [1, 2, 3, *[4, 5], 6]
|
|
hsh = { a: 1, 'b' => 2, **{ c: 3 } }
|
|
nested = [{ x: [1, 2, { y: :z }] }, 99]
|
|
|
|
# --- Multiple assignment / destructuring
|
|
x, y, z = 1, 2, 3
|
|
(a1, (a2, a3)), a4 = [10, [20, 30]], 40
|
|
|
|
# --- Procs, lambdas, blocks
|
|
pr = Proc.new { |u| u * 2 }
|
|
lm = ->(u) { u + 3 }
|
|
|
|
def takes_block(a, b)
|
|
yield a + b if block_given?
|
|
end
|
|
|
|
res1 = takes_block(2, 3) { |v| v * 10 }
|
|
|
|
# --- Enumerators
|
|
enum = [1, 2, 3].map
|
|
enum.each { |e| e * 2 }
|
|
(1..5).lazy.map { |i| i * i }.take(3).force
|
|
|
|
# --- Control flow
|
|
if int_lit > 40
|
|
msg = "greater"
|
|
elsif int_lit == 40
|
|
msg = "equal"
|
|
else
|
|
msg = "less"
|
|
end
|
|
|
|
msg2 = "ok" unless bool_f
|
|
msg3 = "not ok" if !bool_t
|
|
|
|
case int_lit
|
|
when 1..10
|
|
range_case = :small
|
|
when 11, 42
|
|
range_case = :special
|
|
else
|
|
range_case = :other
|
|
end
|
|
|
|
i = 0
|
|
while i < 3
|
|
i += 1
|
|
end
|
|
|
|
j = 3
|
|
until j == 0
|
|
j -= 1
|
|
end
|
|
|
|
for k in [1, 2, 3]
|
|
k
|
|
end
|
|
|
|
loop do
|
|
break
|
|
end
|
|
|
|
# redo example (rare!)
|
|
redo_counter = 0
|
|
[1, 2, 3].each do |n|
|
|
redo_counter += 1
|
|
redo if redo_counter < 1 && n == 1
|
|
end
|
|
|
|
# --- Methods: positional, defaults, keyword, splats
|
|
def args_demo(a, b = 2, *rest, c:, d: 4, **kw, &blk)
|
|
blk.call(a + b + (c || 0) + d) if blk
|
|
[a, b, rest, c, d, kw]
|
|
end
|
|
|
|
args_demo(1, 2, 3, 4, c: 10, d: 20, e: 99) { |sum| sum }
|
|
|
|
# --- Pattern matching (Ruby 2.7+)
|
|
person = { name: "Ada", age: 31, meta: { tags: %w[ruby math] } }
|
|
|
|
case person
|
|
in { name:, age: 31, meta: { tags: [first_tag, *rest_tags] } }
|
|
matched = [name, first_tag, rest_tags]
|
|
else
|
|
matched = :no
|
|
end
|
|
|
|
# pin operator
|
|
expected = 31
|
|
case person
|
|
in { age: ^expected }
|
|
pinned = :ok
|
|
else
|
|
pinned = :nope
|
|
end
|
|
|
|
# array patterns
|
|
arr_pat = [1, 2, 3, 4]
|
|
case arr_pat
|
|
in [first, 2, *tail]
|
|
pattern_arr = [first, tail]
|
|
else
|
|
pattern_arr = []
|
|
end
|
|
|
|
# --- Modules, mixins, refinements
|
|
module Mixin
|
|
def mixed_in
|
|
:mixed
|
|
end
|
|
end
|
|
|
|
module OtherMixin
|
|
def other_mixed
|
|
:other
|
|
end
|
|
end
|
|
|
|
module RefinementExample
|
|
refine String do
|
|
def shout
|
|
upcase + "!"
|
|
end
|
|
end
|
|
end
|
|
|
|
using RefinementExample
|
|
refined_val = "hey".shout
|
|
|
|
# --- Classes, attrs, visibility, singleton methods, eigenclass
|
|
class Base
|
|
include Mixin
|
|
extend OtherMixin
|
|
|
|
CONST_IN_CLASS = :klass
|
|
|
|
@@class_var = 0
|
|
@class_inst = :ci
|
|
|
|
attr_reader :x
|
|
attr_writer :y
|
|
attr_accessor :z
|
|
|
|
def initialize(x, y: 0, **kw)
|
|
@x = x
|
|
@y = y
|
|
@z = kw[:z]
|
|
end
|
|
|
|
def self.build(*a, **k)
|
|
new(*a, **k)
|
|
end
|
|
|
|
class << self
|
|
def in_eigen
|
|
:eigen
|
|
end
|
|
end
|
|
|
|
def public_m
|
|
:public_method
|
|
end
|
|
|
|
protected
|
|
|
|
def prot_m; :protected_method end
|
|
|
|
private
|
|
|
|
def priv_m; :private_method end
|
|
end
|
|
|
|
obj = Base.build(10, y: 20, z: 30)
|
|
obj.z = 99
|
|
_ = obj.mixed_in
|
|
_ = Base.other_mixed
|
|
_ = Base.in_eigen
|
|
|
|
def obj.singleton_thing
|
|
:single
|
|
end
|
|
|
|
# --- Inheritance, super, prepend
|
|
module PrependDemo
|
|
def public_m
|
|
[:prepended, super]
|
|
end
|
|
end
|
|
|
|
class Child < Base
|
|
prepend PrependDemo
|
|
|
|
def initialize(x, y: 0, **kw)
|
|
super
|
|
@child_only = true
|
|
end
|
|
end
|
|
|
|
child = Child.new(1, y: 2, z: 3)
|
|
_ = child.public_m
|
|
|
|
# --- Structs and OpenStruct-like
|
|
Point = Struct.new(:x, :y) do
|
|
def length
|
|
Math.sqrt(x * x + y * y)
|
|
end
|
|
end
|
|
|
|
p0 = Point.new(3, 4)
|
|
_ = p0.length
|
|
|
|
# --- Operators as methods
|
|
class Vec
|
|
attr_reader :x, :y
|
|
def initialize(x, y) @x, @y = x, y end
|
|
def +(o) Vec.new(x + o.x, y + o.y) end
|
|
def -@( ) Vec.new(-x, -y) end
|
|
def [](i) i == 0 ? x : y end
|
|
def []=(i, v) i == 0 ? @x = v : @y = v end
|
|
def to_s() "(#{x},#{y})" end
|
|
end
|
|
|
|
v1 = Vec.new(1, 2)
|
|
v2 = Vec.new(3, 4)
|
|
v3 = v1 + v2
|
|
v4 = -v3
|
|
_ = v4[0]
|
|
v4[1] = 99
|
|
|
|
# --- method_missing / respond_to_missing?
|
|
class Ghost
|
|
def method_missing(name, *a, **k, &b)
|
|
return :dynamic if name.to_s.start_with?("dyn_")
|
|
super
|
|
end
|
|
def respond_to_missing?(name, inc = false)
|
|
name.to_s.start_with?("dyn_") || super
|
|
end
|
|
end
|
|
|
|
g = Ghost.new
|
|
_ = g.dyn_hello
|
|
|
|
# --- Exception handling
|
|
def risky(x)
|
|
raise ArgumentError, "bad!" if x < 0
|
|
x * 2
|
|
rescue ArgumentError => e
|
|
:rescued
|
|
ensure
|
|
:ensured
|
|
end
|
|
|
|
begin
|
|
risky(-1)
|
|
rescue => e
|
|
# retry demo
|
|
@tries ||= 0
|
|
@tries += 1
|
|
retry if @tries < 1
|
|
ensure
|
|
noop = :done
|
|
end
|
|
|
|
# --- Fibers and Threads
|
|
fib = Fiber.new do
|
|
a = Fiber.yield 1
|
|
b = Fiber.yield a + 1
|
|
a + b
|
|
end
|
|
f1 = fib.resume
|
|
f2 = fib.resume(10)
|
|
f3 = fib.resume(5)
|
|
|
|
t = Thread.new { (1..3).map { |n| n * n } }
|
|
thr_val = t.value
|
|
|
|
# --- Backticks, %x, system, __FILE__/__LINE__/__ENCODING__
|
|
file_meta = [__FILE__, __LINE__, __ENCODING__]
|
|
# cmd_out = `echo hi` # (avoid side-effects in a test file)
|
|
# cmd2 = %x(printf "%s" hello)
|
|
# system("true")
|
|
|
|
# --- Keyword lists, %w/%i
|
|
words = %w[alpha beta gamma]
|
|
syms = %i[al be ga]
|
|
|
|
# --- Hash with default proc
|
|
count = Hash.new { |h, k| h[k] = 0 }
|
|
%w[a b a c b a].each { |ch| count[ch] += 1 }
|
|
|
|
# --- Freeze, dup, clone
|
|
frozen = "abc".freeze
|
|
duped = frozen.dup
|
|
cloned = frozen.clone rescue :cloned
|
|
|
|
# --- Marshaling (basic)
|
|
dumped = Marshal.dump([1, 2, 3])
|
|
loaded = Marshal.load(dumped)
|
|
|
|
# --- Encoding / force_encoding
|
|
utf = "héllo"
|
|
forced = utf.dup.force_encoding("ASCII-8BIT")
|
|
|
|
# --- Numeric methods, time
|
|
num = 12.5.round
|
|
now = Time.now
|
|
later = now + 60
|
|
|
|
# --- Random, ranges, case equality ===
|
|
rng = Random.new(1234)
|
|
rand_val = rng.rand(1..10)
|
|
in_range = (1..10) === rand_val
|
|
|
|
# --- Refinement scope check
|
|
using RefinementExample
|
|
refined_again = "yo".shout
|
|
|
|
# --- %r advanced, named captures
|
|
md = /(?<word>\w+)-(?<num>\d+)/.match("abc-123")
|
|
cap_word = md[:word] if md
|
|
|
|
# --- BEGIN/END blocks
|
|
BEGIN {
|
|
# Runs before everything (when file is loaded)
|
|
_begin_marker = :begin_block
|
|
}
|
|
|
|
END {
|
|
# Runs at process exit
|
|
_end_marker = :end_block
|
|
}
|
|
|
|
# --- Numeric literals: hex, oct, bin
|
|
hex = 0xff
|
|
oct = 0o755
|
|
bin = 0b101010
|
|
|
|
# --- Case with guards
|
|
val = 10
|
|
case val
|
|
when Integer if val.even?
|
|
case_guard = :even_int
|
|
else
|
|
case_guard = :other
|
|
end
|
|
|
|
# --- Ternary, safe nav, assignment forms
|
|
tern = val > 5 ? :big : :small
|
|
safe_len = nil&.to_s&.length
|
|
x_plus_eq = 5
|
|
x_plus_eq += 3
|
|
|
|
# --- Ranges of strings, flip-flop (obscure)
|
|
str_rng = "a".."z"
|
|
ff_out = []
|
|
i = 0
|
|
(1..20).each do |n|
|
|
i += 1 if (n == 3)..(n == 6) # flip-flop
|
|
ff_out << i
|
|
end
|
|
|
|
# --- Here-doc with interpolation & indentation
|
|
name = "World"
|
|
msg_hd = <<~MSG
|
|
Hello, #{name}!
|
|
The time is #{Time.now}
|
|
MSG
|
|
|
|
# --- Files (read-only sample)
|
|
# File.read(__FILE__) rescue nil
|
|
|
|
# --- Refinements: nested using
|
|
module Outer
|
|
module Ref
|
|
refine Integer do
|
|
def add2; self + 2 end
|
|
end
|
|
end
|
|
def self.demo
|
|
using Ref
|
|
5.add2
|
|
end
|
|
end
|
|
_ = Outer.demo
|
|
|
|
# --- Keyword argument forwarding (Ruby 3)
|
|
def kf(**kw) kw end
|
|
def wrapper(**kw) kf(**kw) end
|
|
_ = wrapper(a: 1, b: 2)
|
|
|
|
# --- Pattern matching nested hashes and arrays again
|
|
data = {
|
|
user: { id: 1, name: "Ada", roles: %w[admin editor] },
|
|
meta: { active: true }
|
|
}
|
|
|
|
case data
|
|
in { user: { id:, name:, roles: ["admin", *rest] }, meta: { active: true } }
|
|
matched_user = [id, name, rest]
|
|
else
|
|
matched_user = nil
|
|
end
|
|
|
|
# --- Anonymous class & module
|
|
Anon = Class.new do
|
|
def call; :anon end
|
|
end
|
|
ModAnon = Module.new do
|
|
def mod_call; :mod_anon end
|
|
end
|
|
class Anon
|
|
include ModAnon
|
|
end
|
|
_ = Anon.new.call
|
|
_ = Anon.new.mod_call
|
|
|
|
# --- Numeric refinements w/ prepend class method hook
|
|
module P
|
|
def self.prepended(klass); @hooked = klass end
|
|
def to_s; "P(#{super})" end
|
|
end
|
|
|
|
class NWrap
|
|
prepend P
|
|
def to_s; "NWrap" end
|
|
end
|
|
|
|
_ = NWrap.new.to_s
|
|
|
|
# --- Case equality custom
|
|
class Matcher
|
|
def ===(other) other.is_a?(String) && other.start_with?("ok") end
|
|
end
|
|
|
|
case "ok-go"
|
|
when Matcher.new
|
|
_ = :matched
|
|
end
|
|
|
|
# --- Frozen string literal magic comment (simulated usage)
|
|
fsl = +"mutable" # unary + dup
|
|
|
|
# --- Rescue modifier
|
|
risky_value = (1 / 0) rescue :div_by_zero
|
|
|
|
# --- tap/yield_self/then
|
|
tapped = { a: 1 }.tap { |h| h[:b] = 2 }
|
|
ys = 5.yield_self { |n| n * 10 }
|
|
thn = 10.then { |n| n + 1 }
|
|
|
|
# --- Method aliasing
|
|
class AliasDemo
|
|
def orig; :orig end
|
|
alias :alt :orig
|
|
end
|
|
_ = AliasDemo.new.alt
|
|
|
|
# --- Method visibility change
|
|
class VisDemo
|
|
def a; :a end
|
|
def b; :b end
|
|
private :b
|
|
end
|
|
|
|
# --- Keyword args + default nil + double splat
|
|
def kcombo(a, b: nil, **opt) [a, b, opt] end
|
|
_ = kcombo(5, b: 7, c: 9)
|
|
|
|
# --- Hash pattern with defaults via fetch
|
|
hdef = { foo: 1 }
|
|
hdef_v = hdef.fetch(:bar, :default)
|
|
|
|
# --- Refinement + method lookup sanity
|
|
module NumRef
|
|
refine Numeric do
|
|
def sq; self * self end
|
|
end
|
|
end
|
|
using NumRef
|
|
_ = 6.sq
|
|
|
|
# --- rescue with multiple types
|
|
begin
|
|
raise IOError, "io"
|
|
rescue SystemCallError, IOError => e
|
|
@got = e.class
|
|
end
|
|
|
|
# --- ensure altering outer variable
|
|
outer = 0
|
|
begin
|
|
outer = 1
|
|
ensure
|
|
outer += 1
|
|
end
|
|
|
|
# --- Symbol to proc, &: syntax
|
|
doubled = [1, 2, 3].map(&:to_s)
|
|
|
|
# --- Safe pattern matching nil
|
|
case nil
|
|
in Integer
|
|
:nope
|
|
else
|
|
:ok_nil
|
|
end
|
|
|
|
# --- Hash literal with trailing comma, labeled args-like
|
|
cfg = {
|
|
host: "localhost",
|
|
port: 5432,
|
|
}
|
|
|
|
# --- Numeric separators and exponents
|
|
exp = 1.23e-4
|
|
big = 1_000_000
|
|
|
|
# --- %i/%w with interpolation (only in %W)
|
|
inter = "X"
|
|
words2 = %W[a b #{inter}]
|
|
|
|
# --- Dir, ENV (read only)
|
|
_ = Dir.pwd
|
|
_ = ENV["SHELL"]
|
|
|
|
# --- END of the kitchen sink
|
|
puts "Loaded kitchen_sink.rb with many Ruby constructs." if __FILE__ == $0
|