A fork of Crisp for HARP
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

139 lines
3.3 KiB

  1. require "./expr"
  2. require "./error"
  3. module Crisp
  4. extend self
  5. class Reader
  6. def initialize(@tokens : Array(String))
  7. @pos = 0
  8. end
  9. def current_token
  10. @tokens[@pos] rescue nil
  11. end
  12. def peek
  13. t = current_token
  14. if t && t[0] == ';'
  15. @pos += 1
  16. peek
  17. else
  18. t
  19. end
  20. end
  21. def next
  22. peek
  23. ensure
  24. @pos += 1
  25. end
  26. def read_sequence(init, open, close)
  27. token = self.next
  28. Crisp.parse_error "expected '#{open}', got EOF" unless token
  29. Crisp.parse_error "expected '#{open}', got #{token}" unless token[0] == open
  30. loop do
  31. token = peek
  32. Crisp.parse_error "expected '#{close}', got EOF" unless token
  33. break if token[0] == close
  34. init << read_form
  35. peek
  36. end
  37. self.next
  38. init
  39. end
  40. def read_list
  41. Crisp::Expr.new read_sequence(Crisp::List.new, '(', ')')
  42. end
  43. def read_vector
  44. Crisp::Expr.new read_sequence(Crisp::Vector.new, '[', ']')
  45. end
  46. def read_hashmap
  47. types = read_sequence([] of Crisp::Expr, '{', '}')
  48. Crisp.parse_error "odd number of elements for hash-map: #{types.size}" if types.size.odd?
  49. map = Crisp::HashMap.new
  50. types.each_slice(2) do |kv|
  51. k, v = kv[0].unwrap, kv[1]
  52. case k
  53. when String
  54. map[k] = v
  55. else
  56. Crisp.parse_error("key of hash-map must be string or keyword")
  57. end
  58. end
  59. Crisp::Expr.new map
  60. end
  61. def read_atom
  62. token = self.next
  63. Crisp.parse_error "expected Atom but got EOF" unless token
  64. Crisp::Expr.new case
  65. when token =~ /^-?\d+$/ then token.to_i
  66. when token == "true" then true
  67. when token == "false" then false
  68. when token == "nil" then nil
  69. when token[0] == '"' then token[1..-2].gsub(/\\"/, "\"")
  70. when token[0] == ':' then "\u029e#{token[1..-1]}"
  71. else Crisp::Symbol.new token
  72. end
  73. end
  74. def list_of(symname)
  75. Crisp::List.new << gen_type(Crisp::Symbol, symname) << read_form
  76. end
  77. def read_form
  78. token = peek
  79. Crisp.parse_error "unexpected EOF" unless token
  80. Crisp.parse_error "unexpected comment" if token[0] == ';'
  81. Crisp::Expr.new case token
  82. when "(" then read_list
  83. when ")" then Crisp.parse_error "unexpected ')'"
  84. when "[" then read_vector
  85. when "]" then Crisp.parse_error "unexpected ']'"
  86. when "{" then read_hashmap
  87. when "}" then Crisp.parse_error "unexpected '}'"
  88. when "'" then self.next; list_of("quote")
  89. when "`" then self.next; list_of("quasiquote")
  90. when "~" then self.next; list_of("unquote")
  91. when "~@" then self.next; list_of("splice-unquote")
  92. when "@" then self.next; list_of("deref")
  93. when "^"
  94. self.next
  95. meta = read_form
  96. list_of("with-meta") << meta
  97. else read_atom
  98. end
  99. end
  100. end
  101. def tokenize(str)
  102. regex = /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)/
  103. str.scan(regex).map { |m| m[1] }.reject(&.empty?)
  104. end
  105. def read_str(str)
  106. r = Reader.new(tokenize(str))
  107. begin
  108. r.read_form
  109. ensure
  110. unless r.peek.nil?
  111. raise Crisp::ParseException.new "expected EOF, got #{r.peek.to_s}"
  112. end
  113. end
  114. end
  115. end