SIGN IN SIGN UP
2021-11-19 20:32:12 -07:00
require_relative "errors"
require_relative "types"
module Mal
extend self
TOKEN_REGEX = /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]*)/
def read_atom(reader)
case reader.peek
2021-11-26 17:00:10 -07:00
when /\A"(?:\\.|[^\\"])*"\z/
2021-11-19 20:32:12 -07:00
read_string(reader)
2021-11-26 17:00:10 -07:00
when /\A"/
raise UnbalancedStringError, "unbalanced string << #{reader.peek.inspect} >>"
2021-11-19 20:32:12 -07:00
when /\A:/
read_keyword(reader)
when "nil"
read_nil(reader)
when "true"
read_true(reader)
when "false"
read_false(reader)
2021-11-21 15:57:26 -07:00
when /\A-?\d+(\.\d+)?/
2021-11-19 20:32:12 -07:00
read_number(reader)
when /\A;/
raise SkipCommentError
2021-11-19 20:32:12 -07:00
else
read_symbol(reader)
end
end
def read_deref(reader)
list = Types::List.new
2021-11-22 11:34:21 -07:00
list << Types::Symbol.for("deref")
2021-11-19 20:32:12 -07:00
list << read_form(reader)
list
end
def read_false(reader)
2021-11-22 11:34:21 -07:00
reader.advance!
Types::False.instance
2021-11-19 20:32:12 -07:00
end
def read_form(reader)
case reader.peek
when "'"
read_quote(reader.advance!)
when "`"
read_quasiquote(reader.advance!)
when "~"
read_unquote(reader.advance!)
when "~@"
read_splice_unquote(reader.advance!)
when "@"
read_deref(reader.advance!)
when "^"
read_with_metadata(reader.advance!)
when "("
read_list(reader.advance!)
when "["
read_vector(reader.advance!)
when "{"
read_hashmap(reader.advance!)
else
read_atom(reader)
end
end
def read_hashmap(reader)
hashmap = Types::Hashmap.new
until reader.peek == "}"
key = read_form(reader)
unless Types::String === key || Types::Keyword === key
raise InvalidHashmapKeyError, "invalid hashmap key, must be string or keyword"
2021-11-19 20:32:12 -07:00
end
if reader.peek != "}"
value = read_form(reader)
else
raise UnbalancedHashmapError, "unbalanced hashmap error, missing closing '}'"
2021-11-19 20:32:12 -07:00
end
hashmap[key] = value
end
reader.advance!
hashmap
rescue Error => e
case e
when InvalidReaderPositionError
raise UnbalancedHashmapError, "unbalanced hashmap error, missing closing '}'"
2021-11-19 20:32:12 -07:00
else
raise e
end
end
def read_keyword(reader)
value = reader.next.dup[1...]
substitute_escaped_chars!(value)
2021-11-22 11:34:21 -07:00
Types::Keyword.for(value)
2021-11-19 20:32:12 -07:00
end
def read_list(reader)
list = Types::List.new
until reader.peek == ")"
list << read_form(reader)
end
reader.advance!
list
rescue Error => e
case e
when InvalidReaderPositionError
raise UnbalancedListError, "unbalanced list error, missing closing ')'"
2021-11-19 20:32:12 -07:00
else
raise e
end
end
def read_nil(reader)
2021-11-22 11:34:21 -07:00
reader.advance!
Types::Nil.instance
2021-11-19 20:32:12 -07:00
end
def read_number(reader)
case reader.peek
when /\d+\.\d+/
Types::Number.new(reader.next.to_f)
when /\d+/
Types::Number.new(reader.next.to_i)
else
raise InvalidTypeError, "invalid number syntax, only supports integers/floats"
2021-11-19 20:32:12 -07:00
end
end
def read_quasiquote(reader)
list = Types::List.new
2021-11-22 11:34:21 -07:00
list << Types::Symbol.for("quasiquote")
2021-11-19 20:32:12 -07:00
list << read_form(reader)
list
end
def read_quote(reader)
list = Types::List.new
2021-11-22 11:34:21 -07:00
list << Types::Symbol.for("quote")
2021-11-19 20:32:12 -07:00
list << read_form(reader)
list
end
def read_splice_unquote(reader)
list = Types::List.new
2021-11-22 11:34:21 -07:00
list << Types::Symbol.for("splice-unquote")
2021-11-19 20:32:12 -07:00
list << read_form(reader)
list
end
def read_str(input)
tokenized = tokenize(input)
raise SkipCommentError if tokenized.empty?
read_form(Reader.new(tokenized))
2021-11-19 20:32:12 -07:00
end
def read_string(reader)
raw_value = reader.next.dup
2021-11-26 17:00:10 -07:00
value = raw_value[1...-1]
substitute_escaped_chars!(value)
2021-11-19 20:32:12 -07:00
if raw_value.length <= 1 || raw_value[-1] != '"'
raise UnbalancedStringError, "unbalanced string error, missing closing '\"'"
2021-11-19 20:32:12 -07:00
end
Types::String.new(value)
end
def read_symbol(reader)
2021-11-22 11:34:21 -07:00
Types::Symbol.for(reader.next)
2021-11-19 20:32:12 -07:00
end
def read_true(reader)
2021-11-22 11:34:21 -07:00
reader.advance!
Types::True.instance
2021-11-19 20:32:12 -07:00
end
def read_unquote(reader)
list = Types::List.new
2021-11-22 11:34:21 -07:00
list << Types::Symbol.for("unquote")
2021-11-19 20:32:12 -07:00
list << read_form(reader)
list
end
def read_vector(reader)
vector = Types::Vector.new
until reader.peek == "]"
vector << read_form(reader)
end
reader.advance!
vector
rescue Error => e
case e
when InvalidReaderPositionError
raise UnbalancedVectorError, "unbalanced vector error, missing closing ']'"
2021-11-19 20:32:12 -07:00
else
raise e
end
end
def read_with_metadata(reader)
list = Types::List.new
2021-11-22 11:34:21 -07:00
list << Types::Symbol.for("with-meta")
2021-11-19 20:32:12 -07:00
first = read_form(reader)
second = read_form(reader)
list << second
list << first
list
end
def tokenize(input)
input.scan(TOKEN_REGEX).flatten.each_with_object([]) do |token, tokens|
if token != "" && !token.start_with?(";")
2021-11-19 20:32:12 -07:00
tokens << token
end
end
end
class Reader
attr_reader :tokens
def initialize(tokens)
@position = 0
@tokens = tokens
end
def advance!
@position += 1
self
end
def next
value = peek
@position += 1
value
end
def peek
if @position > @tokens.size - 1
raise InvalidReaderPositionError, "invalid reader position error, unable to parse mal expression"
2021-11-19 20:32:12 -07:00
end
@tokens[@position]
end
end
private
def substitute_escaped_chars!(string_or_keyword)
2021-11-26 17:00:10 -07:00
string_or_keyword.gsub!(/\\./, {"\\\\" => "\\", "\\n" => "\n", "\\\"" => '"'})
2021-11-19 20:32:12 -07:00
end
end