A fork of Crisp for HARP
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

303 lignes
8.1 KiB

  1. require "colorize"
  2. require "readline"
  3. require "./reader"
  4. require "./printer"
  5. require "./types"
  6. require "./env"
  7. require "./core"
  8. require "./error"
  9. # Note:
  10. # Employed downcase names because Crystal prohibits uppercase names for methods
  11. module Crisp
  12. extend self
  13. def func_of(env, binds, body)
  14. -> (args : Array(Crisp::Type)) {
  15. new_env = Crisp::Env.new(env, binds, args)
  16. eval(body, new_env)
  17. } as Crisp::Func
  18. end
  19. def eval_ast(ast, env)
  20. return ast.map{|n| eval(n, env) as Crisp::Type} if ast.is_a? Array
  21. val = ast.unwrap
  22. Crisp::Type.new case val
  23. when Crisp::Symbol
  24. if e = env.get(val.str)
  25. e
  26. else
  27. eval_error "'#{val.str}' not found"
  28. end
  29. when Crisp::List
  30. val.each_with_object(Crisp::List.new){|n, l| l << eval(n, env)}
  31. when Crisp::Vector
  32. val.each_with_object(Crisp::Vector.new){|n, l| l << eval(n, env)}
  33. when Crisp::HashMap
  34. new_map = Crisp::HashMap.new
  35. val.each{|k, v| new_map[k] = eval(v, env)}
  36. new_map
  37. else
  38. val
  39. end
  40. end
  41. def read(str)
  42. read_str str
  43. end
  44. macro pair?(list)
  45. {{list}}.is_a?(Array) && !{{list}}.empty?
  46. end
  47. def quasiquote(ast)
  48. list = ast.unwrap
  49. unless pair?(list)
  50. return Crisp::Type.new(
  51. Crisp::List.new << gen_type(Crisp::Symbol, "quote") << ast
  52. )
  53. end
  54. head = list.first.unwrap
  55. case
  56. # ("unquote" ...)
  57. when head.is_a?(Crisp::Symbol) && head.str == "unquote"
  58. list[1]
  59. # (("splice-unquote" ...) ...)
  60. when pair?(head) && (arg0 = head.first.unwrap).is_a?(Crisp::Symbol) && arg0.str == "splice-unquote"
  61. tail = Crisp::Type.new list[1..-1].to_crisp_value
  62. Crisp::Type.new(
  63. Crisp::List.new << gen_type(Crisp::Symbol, "concat") << head[1] << quasiquote(tail)
  64. )
  65. else
  66. tail = Crisp::Type.new list[1..-1].to_crisp_value
  67. Crisp::Type.new(
  68. Crisp::List.new << gen_type(Crisp::Symbol, "cons") << quasiquote(list.first) << quasiquote(tail)
  69. )
  70. end
  71. end
  72. def macro_call?(ast, env)
  73. list = ast.unwrap
  74. return false unless list.is_a? Crisp::List
  75. return false if list.empty?
  76. sym = list.first.unwrap
  77. return false unless sym.is_a? Crisp::Symbol
  78. func = env.find(sym.str).try(&.data[sym.str])
  79. return false unless func && func.macro?
  80. true
  81. end
  82. def macroexpand(ast, env)
  83. while macro_call?(ast, env)
  84. # Already checked in macro_call?
  85. list = ast.unwrap as Crisp::List
  86. func_sym = list[0].unwrap as Crisp::Symbol
  87. func = env.get(func_sym.str).unwrap
  88. case func
  89. when Crisp::Func
  90. ast = func.call(list[1..-1])
  91. when Crisp::Closure
  92. ast = func.fn.call(list[1..-1])
  93. else
  94. eval_error "macro '#{func_sym.str}' must be function: #{ast}"
  95. end
  96. end
  97. ast
  98. end
  99. macro invoke_list(l, env)
  100. f = eval({{l}}.first, {{env}}).unwrap
  101. args = eval_ast({{l}}[1..-1], {{env}}) as Array
  102. case f
  103. when Crisp::Closure
  104. ast = f.ast
  105. {{env}} = Crisp::Env.new(f.env, f.params, args)
  106. next # TCO
  107. when Crisp::Func
  108. return f.call args
  109. else
  110. eval_error "expected function as the first argument: #{f}"
  111. end
  112. end
  113. def debug(ast)
  114. puts print(ast).colorize.red
  115. end
  116. def eval(ast, env)
  117. # 'next' in 'do...end' has a bug in crystal 0.7.1
  118. # https://github.com/manastech/crystal/issues/659
  119. while true
  120. return eval_ast(ast, env) unless ast.unwrap.is_a? Crisp::List
  121. ast = macroexpand(ast, env)
  122. list = ast.unwrap
  123. return ast unless list.is_a? Crisp::List
  124. return ast if list.empty?
  125. head = list.first.unwrap
  126. return invoke_list(list, env) unless head.is_a? Crisp::Symbol
  127. return Crisp::Type.new case head.str
  128. when "def!"
  129. eval_error "wrong number of argument for 'def!'" unless list.size == 3
  130. a1 = list[1].unwrap
  131. eval_error "1st argument of 'def!' must be symbol: #{a1}" unless a1.is_a? Crisp::Symbol
  132. env.set(a1.str, eval(list[2], env))
  133. when "let*"
  134. eval_error "wrong number of argument for 'def!'" unless list.size == 3
  135. bindings = list[1].unwrap
  136. eval_error "1st argument of 'let*' must be list or vector" unless bindings.is_a? Array
  137. eval_error "size of binding list must be even" unless bindings.size.even?
  138. new_env = Crisp::Env.new env
  139. bindings.each_slice(2) do |binding|
  140. key, value = binding
  141. name = key.unwrap
  142. eval_error "name of binding must be specified as symbol #{name}" unless name.is_a? Crisp::Symbol
  143. new_env.set(name.str, eval(value, new_env))
  144. end
  145. ast, env = list[2], new_env
  146. next # TCO
  147. when "do"
  148. if list.empty?
  149. ast = Crisp::Type.new nil
  150. next
  151. end
  152. eval_ast(list[1..-2].to_crisp_value, env)
  153. ast = list.last
  154. next # TCO
  155. when "if"
  156. ast = unless eval(list[1], env).unwrap
  157. list.size >= 4 ? list[3] : Crisp::Type.new(nil)
  158. else
  159. list[2]
  160. end
  161. next # TCO
  162. when "fn*"
  163. params = list[1].unwrap
  164. unless params.is_a? Array
  165. eval_error "'fn*' parameters must be list or vector: #{params}"
  166. end
  167. Crisp::Closure.new(list[2], params, env, func_of(env, params, list[2]))
  168. when "quote"
  169. list[1]
  170. when "quasiquote"
  171. ast = quasiquote list[1]
  172. next # TCO
  173. when "defmacro!"
  174. eval_error "wrong number of argument for 'defmacro!'" unless list.size == 3
  175. a1 = list[1].unwrap
  176. eval_error "1st argument of 'defmacro!' must be symbol: #{a1}" unless a1.is_a? Crisp::Symbol
  177. env.set(a1.str, eval(list[2], env).tap{|n| n.is_macro = true})
  178. when "macroexpand"
  179. macroexpand(list[1], env)
  180. when "try*"
  181. catch_list = list[2].unwrap
  182. return eval(list[1], env) unless catch_list.is_a? Crisp::List
  183. catch_head = catch_list.first.unwrap
  184. return eval(list[1], env) unless catch_head.is_a? Crisp::Symbol
  185. return eval(list[1], env) unless catch_head.str == "catch*"
  186. begin
  187. eval(list[1], env)
  188. rescue e : Crisp::RuntimeException
  189. new_env = Crisp::Env.new(env, [catch_list[1]], [e.thrown])
  190. eval(catch_list[2], new_env)
  191. rescue e
  192. new_env = Crisp::Env.new(env, [catch_list[1]], [Crisp::Type.new e.message])
  193. eval(catch_list[2], new_env)
  194. end
  195. else
  196. invoke_list(list, env)
  197. end
  198. end
  199. end
  200. def print(result)
  201. pr_str(result, true)
  202. end
  203. def rep(str)
  204. print(eval(read(str), $repl_env))
  205. end
  206. class ProgramState
  207. def rep(str)
  208. print(eval(read(str), @env))
  209. end
  210. def initialize(args)
  211. @env = Crisp::Env.new nil
  212. Crisp::NameSpace.each{|k,v| @curent_env.set(k, Crisp::Type.new(v))}
  213. @env.set("eval", Crisp::Type.new -> (args: Array(Crisp::Type)){ Crisp.eval(args[0], @env) })
  214. rep "(def! not (fn* (a) (if a false true)))"
  215. rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"
  216. rep "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))"
  217. rep "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))"
  218. rep "(def! *host-language* \"crystal\")"
  219. argv = Crisp::List.new
  220. if args
  221. args.each do |a|
  222. argv << Crisp::Type.new a
  223. end
  224. end
  225. @env.set("*ARGV*", Crisp::Type.new argv)
  226. end
  227. end
  228. class Interpreter
  229. def initialize(args)
  230. @state = ProgramState.new args
  231. end
  232. def run(filename = nil)
  233. if filename
  234. begin
  235. Crisp.rep "(load-file \"#{filename}\")"
  236. rescue e
  237. STDERR.puts e
  238. exit 1
  239. end
  240. exit
  241. end
  242. while line = Readline.readline("Crisp> ", true)
  243. begin
  244. puts @state.rep(line)
  245. rescue e
  246. STDERR.puts e
  247. end
  248. end
  249. end
  250. end
  251. end