Last active
January 20, 2021 14:28
-
-
Save noredeen/5d9c3244504dd02209d2c461170f167b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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