Skip to content

Instantly share code, notes, and snippets.

@noredeen
Last active January 20, 2021 14:28
Show Gist options
  • Save noredeen/5d9c3244504dd02209d2c461170f167b to your computer and use it in GitHub Desktop.
Save noredeen/5d9c3244504dd02209d2c461170f167b to your computer and use it in GitHub Desktop.
require 'strscan'
# Methods can be added here to expand functionality available to the REPL
module Methods
def self.add(*nums)
nums.reduce(&:+)
end
def self.multiply(*nums)
nums.reduce(&:*)
end
end
# This parser supports any number of methods and arguments (including zero)
class Parser
def initialize(str)
@scanner = StringScanner.new(str)
end
def parse
read_arg
end
private
# Expects "add 1 2 3 4)" or "add)" or ")"
def read_expr
method = read_token.strip
args = []
until @scanner.peek(1) == ')'
arg = read_arg
raise BadSyntaxError, pretty_current if arg == false
args << arg
end
@scanner.skip_until(/\s/) # To continue to the next expression if it exists
evaluate(method, *args)
end
def read_arg
first_char = @scanner.peek(1)
case first_char
when '('
@scanner.getch
read_expr
when ->(x) { numeric?(x) }
num = read_token
return false if num.blank?
num.strip.to_i
else
false
end
end
def read_token
after_bracket = @scanner.check_until(/\s/)&.include?(')')
(after_bracket ? @scanner.scan_until(/[A-Za-z]*(?=\))/) : @scanner.scan_until(/\s|.(?=\))/))
end
def evaluate(method, *args)
return nil if method.blank? # "()" becomes a nil argument
return Methods.public_send(method) unless args # To support methods w/out args
Methods.public_send(method, *args)
end
def numeric?(str)
Float(str) != nil rescue false
end
# Prints: (add 3 ()))))
# ^
def pretty_current
"\n#{@scanner.string}\n#{' ' * @scanner.pos}^\n"
end
class BadSyntaxError < StandardError; end
end
# ===== Quick monkeypatches for cleaner code =====
class String
# Remove trailing bracket along with spaces
def strip
stripped = lstrip.rstrip
return stripped[0...-1] if stripped[-1] == ')'
stripped
end
end
class Object
def blank?
respond_to?(:empty?) ? !!empty? : !self
end
end
# ================================================
puts Parser.new(ARGV[0]).parse
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment