require "./expr" require "./error" module Crisp extend self class Reader def initialize(@tokens : Array(String)) @pos = 0 end def current_token @tokens[@pos] rescue nil end def peek t = current_token if t && t[0] == ';' @pos += 1 peek else t end end def next peek ensure @pos += 1 end def read_sequence(init, open, close) token = self.next Crisp.parse_error "expected '#{open}', got EOF" unless token Crisp.parse_error "expected '#{open}', got #{token}" unless token[0] == open loop do token = peek Crisp.parse_error "expected '#{close}', got EOF" unless token break if token[0] == close init << read_form peek end self.next init end def read_list Crisp::Expr.new read_sequence(Crisp::List.new, '(', ')') end def read_vector Crisp::Expr.new read_sequence(Crisp::Vector.new, '[', ']') end def read_hashmap types = read_sequence([] of Crisp::Expr, '{', '}') Crisp.parse_error "odd number of elements for hash-map: #{types.size}" if types.size.odd? map = Crisp::HashMap.new types.each_slice(2) do |kv| k, v = kv[0].unwrap, kv[1] case k when String map[k] = v else Crisp.parse_error("key of hash-map must be string or keyword") end end Crisp::Expr.new map end def read_atom token = self.next Crisp.parse_error "expected Atom but got EOF" unless token Crisp::Expr.new case when token =~ /^-?\d+$/ then token.to_i when token == "true" then true when token == "false" then false when token == "nil" then nil when token[0] == '"' then token[1..-2].gsub(/\\"/, "\"") when token[0] == ':' then "\u029e#{token[1..-1]}" else Crisp::Symbol.new token end end def list_of(symname) Crisp::List.new << gen_type(Crisp::Symbol, symname) << read_form end def read_form token = peek Crisp.parse_error "unexpected EOF" unless token Crisp.parse_error "unexpected comment" if token[0] == ';' Crisp::Expr.new case token when "(" then read_list when ")" then Crisp.parse_error "unexpected ')'" when "[" then read_vector when "]" then Crisp.parse_error "unexpected ']'" when "{" then read_hashmap when "}" then Crisp.parse_error "unexpected '}'" when "'" then self.next; list_of("quote") when "`" then self.next; list_of("quasiquote") when "~" then self.next; list_of("unquote") when "~@" then self.next; list_of("splice-unquote") when "@" then self.next; list_of("deref") when "^" self.next meta = read_form list_of("with-meta") << meta else read_atom end end end def tokenize(str) regex = /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)/ str.scan(regex).map { |m| m[1] }.reject(&.empty?) end def read_str(str) r = Reader.new(tokenize(str)) begin r.read_form ensure unless r.peek.nil? raise Crisp::ParseException.new "expected EOF, got #{r.peek.to_s}" end end end end