apricot 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.ruby-version +1 -1
  4. data/.travis.yml +1 -0
  5. data/Gemfile.lock +229 -11
  6. data/README.md +46 -29
  7. data/Rakefile +1 -1
  8. data/apricot.gemspec +7 -3
  9. data/benchmarks/factorial.rb +51 -0
  10. data/benchmarks/interpolate.rb +20 -0
  11. data/bin/apricot +5 -23
  12. data/examples/bot.apr +1 -4
  13. data/examples/cinch-bot.apr +3 -3
  14. data/examples/sinatra.apr +9 -0
  15. data/kernel/core.apr +124 -75
  16. data/kernel/repl.apr +37 -0
  17. data/lib/apricot.rb +7 -26
  18. data/lib/apricot/boot.rb +24 -0
  19. data/lib/apricot/code_loader.rb +108 -0
  20. data/lib/apricot/compiler.rb +265 -32
  21. data/lib/apricot/generator.rb +10 -3
  22. data/lib/apricot/identifier.rb +25 -10
  23. data/lib/apricot/list.rb +28 -41
  24. data/lib/apricot/macroexpand.rb +14 -8
  25. data/lib/apricot/misc.rb +2 -1
  26. data/lib/apricot/namespace.rb +20 -3
  27. data/lib/apricot/{parser.rb → reader.rb} +221 -194
  28. data/lib/apricot/repl.rb +67 -24
  29. data/lib/apricot/ruby_ext.rb +27 -16
  30. data/lib/apricot/scopes.rb +159 -0
  31. data/lib/apricot/seq.rb +43 -1
  32. data/lib/apricot/special_forms.rb +16 -695
  33. data/lib/apricot/special_forms/def.rb +32 -0
  34. data/lib/apricot/special_forms/do.rb +23 -0
  35. data/lib/apricot/special_forms/dot.rb +112 -0
  36. data/lib/apricot/special_forms/fn.rb +342 -0
  37. data/lib/apricot/special_forms/if.rb +31 -0
  38. data/lib/apricot/special_forms/let.rb +8 -0
  39. data/lib/apricot/special_forms/loop.rb +10 -0
  40. data/lib/apricot/special_forms/quote.rb +9 -0
  41. data/lib/apricot/special_forms/recur.rb +26 -0
  42. data/lib/apricot/special_forms/try.rb +146 -0
  43. data/lib/apricot/variables.rb +65 -0
  44. data/lib/apricot/version.rb +1 -1
  45. data/spec/compiler_spec.rb +53 -450
  46. data/spec/fn_spec.rb +206 -0
  47. data/spec/list_spec.rb +1 -1
  48. data/spec/reader_spec.rb +349 -0
  49. data/spec/spec_helper.rb +40 -4
  50. data/spec/special_forms_spec.rb +203 -0
  51. metadata +99 -133
  52. data/lib/apricot/ast.rb +0 -3
  53. data/lib/apricot/ast/identifier.rb +0 -111
  54. data/lib/apricot/ast/list.rb +0 -99
  55. data/lib/apricot/ast/literals.rb +0 -240
  56. data/lib/apricot/ast/node.rb +0 -45
  57. data/lib/apricot/ast/scopes.rb +0 -147
  58. data/lib/apricot/ast/toplevel.rb +0 -66
  59. data/lib/apricot/ast/variables.rb +0 -64
  60. data/lib/apricot/printers.rb +0 -12
  61. data/lib/apricot/stages.rb +0 -60
  62. data/spec/parser_spec.rb +0 -312
@@ -0,0 +1,20 @@
1
+ $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
2
+ require 'apricot'
3
+
4
+ begin
5
+ require 'benchmark/ips'
6
+ rescue LoadError
7
+ $stderr.puts "The benchmark-ips gem is not installed."
8
+ exit 1
9
+ end
10
+
11
+ apr = Apricot::Compiler.eval <<CODE
12
+ #(str "hello. " 42 " is the best number")
13
+ CODE
14
+
15
+ rbx = lambda { "hello. #{42} is the best number" }
16
+
17
+ Benchmark.ips do |x|
18
+ x.report("rbx", &rbx)
19
+ x.report("apr", &apr)
20
+ end
@@ -1,20 +1,7 @@
1
- #!/usr/bin/env rbx
2
- # vim: ft=ruby:
3
-
4
- unless defined?(Rubinius)
5
- $stderr.puts "Error: Apricot must be run on Rubinius."
6
- exit 1
7
- end
8
-
9
- unless Rubinius.ruby19?
10
- $stderr.puts "Warning: Apricot must be run in Ruby 1.9 mode, executing rbx -X19..."
11
- exec("/usr/bin/env", "rbx", "-X19", file, *ARGV)
12
- end
13
-
1
+ #!/usr/bin/env ruby
14
2
  require 'apricot'
15
3
 
16
4
  evals = []
17
- bytecode = false
18
5
 
19
6
  options = Rubinius::Options.new "Usage: #{$0} [options] [program]", 20
20
7
  options.doc "OPTIONS:"
@@ -23,10 +10,6 @@ options.on "-e", "CODE", "evaluate CODE and print the result" do |code|
23
10
  evals << [:eval, code]
24
11
  end
25
12
 
26
- options.on "-B", "--bytecode", "print bytecode after compiling" do
27
- bytecode = true
28
- end
29
-
30
13
  options.on "-h", "--help", "display this help" do
31
14
  puts options
32
15
  exit
@@ -39,7 +22,7 @@ end
39
22
  if evals.empty?
40
23
  if $stdin.tty?
41
24
  require 'apricot/repl'
42
- Apricot::REPL.new('apr> ', bytecode).run
25
+ Apricot::REPL.new.run
43
26
  else
44
27
  evals << [:stdin]
45
28
  end
@@ -48,11 +31,10 @@ end
48
31
  evals.each do |type, *args|
49
32
  case type
50
33
  when :eval
51
- Apricot::Compiler.eval(args.first, "(eval)", 1, bytecode)
34
+ Apricot::Compiler.eval(args.first, "(eval)", 1)
52
35
  when :stdin
53
- Apricot::Compiler.eval(STDIN.read, "(stdin)", 1, bytecode)
36
+ Apricot::Compiler.eval(STDIN.read, "(stdin)", 1)
54
37
  when :file
55
- # The code is executed as it compiles.
56
- Apricot::Compiler.compile(args.first, nil, bytecode)
38
+ Apricot::CodeLoader.require(File.expand_path(args.first))
57
39
  end
58
40
  end
@@ -1,7 +1,4 @@
1
- ; An example IRC bot. This does not reflect our vision for Apricot, just what
2
- ; features we currently have working.
3
-
4
- (require "socket")
1
+ (require-ruby "socket")
5
2
 
6
3
  (defn send [io & parts]
7
4
  (let [msg (apply str parts)]
@@ -1,10 +1,10 @@
1
- (require "cinch")
1
+ (require-ruby "cinch")
2
2
 
3
3
  (doto (Cinch::Bot.)
4
4
  (.configure | #(doto %
5
- (.server= "irc.tenthbit.net")
5
+ (.server= "irc.freenode.net")
6
6
  (.nick= "apribot")
7
- (.channels= ["#programming"])))
7
+ (.channels= ["#apricot"])))
8
8
 
9
9
  (.on :message #r/^apribot[:,]?\s+(.+)/
10
10
  | (fn [m msg] (.reply m msg true)))
@@ -0,0 +1,9 @@
1
+ (require-ruby "sinatra")
2
+
3
+ (defmacro get [route & body]
4
+ `(.send MAIN :get ~route | (fn ~@body)))
5
+
6
+ (get "/hello/:name" [name]
7
+ (str "Hello " name))
8
+
9
+ (.run! Sinatra::Application)
@@ -25,10 +25,10 @@
25
25
  {})
26
26
  arglists (if (.is_a? (.first body) Array)
27
27
  (list (.first body))
28
- (.to_list
29
- (.map body | #(if (.is_a? % Apricot::List)
28
+ (.to_seq
29
+ (.map body | #(if (.is_a? % Apricot::Seq)
30
30
  (.first %)))))
31
- f (.intern Apricot::Identifier (.gensym Apricot))]
31
+ f (Apricot/gensym)]
32
32
  (list 'let [f (concat (list 'fn name) body)]
33
33
  (list 'def name f)
34
34
  (list '.apricot_meta=
@@ -53,7 +53,7 @@
53
53
  [name doc-string? metadata? ([params ...] body) ...+])
54
54
  :macro true}
55
55
  [name & body]
56
- (let [f (.intern Apricot::Identifier (.gensym Apricot))]
56
+ (let [f (Apricot/gensym)]
57
57
  (list 'let [f (concat (list 'defn name) body)]
58
58
  (list '.store (list '.apricot_meta f) :macro true)
59
59
  f)))
@@ -62,6 +62,26 @@
62
62
  "Create a new list containing the items."
63
63
  [& items] (.to_list items))
64
64
 
65
+ (defn seq
66
+ "Return a seq on the collection. If the collection is empty, return nil.
67
+ (seq nil) returns nil."
68
+ [coll] (.to_seq coll))
69
+
70
+ (defn first
71
+ "Return the first item in the collection. Call seq on the argument. If coll
72
+ is nil, return nil."
73
+ [coll] (.first (seq coll)))
74
+
75
+ (defn rest
76
+ "Return a possibly empty seq of the items after the first. Call seq on the
77
+ argument."
78
+ [coll] (.rest (seq coll)))
79
+
80
+ (defn next
81
+ "Return a seq of the items after the first. Call seq on the argument. If
82
+ there are no more items, return nil."
83
+ [coll] (.next (seq coll)))
84
+
65
85
  (defn concat
66
86
  "Concatenate the items in the supplied colls into a single list."
67
87
  [& colls] (.to_list (.reduce (.map colls | :to_a) [] :+)))
@@ -71,8 +91,16 @@
71
91
  [& items] items)
72
92
 
73
93
  (defn set
74
- "Create a new set containing the items."
75
- [& items] (Set. items))
94
+ "Return a set of the distinct elements of coll."
95
+ [coll] (Set. coll))
96
+
97
+ (defn hash-set
98
+ "Return a new hash set with supplied keys."
99
+ [& keys] (Set. keys))
100
+
101
+ (defn sorted-set
102
+ "Return a new sorted set with supplied keys."
103
+ [& keys] (SortedSet. keys))
76
104
 
77
105
  (defn hash
78
106
  "Create a new hash map from the items. The items are interpreted as a list of
@@ -88,6 +116,25 @@
88
116
  "Return a new list where head is the first element and tail is the rest."
89
117
  [head tail] (Apricot::Cons. head tail))
90
118
 
119
+ (defn spread
120
+ {:private true}
121
+ [arglist]
122
+ (if (.nil? arglist)
123
+ nil
124
+ (if (.nil? (next arglist))
125
+ (seq (first arglist))
126
+ (cons (first arglist) (spread (next arglist))))))
127
+
128
+ (defn list*
129
+ "Creates a new list containing the items prepended to the rest, the last of
130
+ which will be treated as a sequence."
131
+ ([args] (seq args))
132
+ ([a args] (cons a args))
133
+ ([a b args] (cons a (cons b args)))
134
+ ([a b c args] (cons a (cons b (cons c args))))
135
+ ([a b c d & more]
136
+ (cons a (cons b (cons c (cons d (spread more)))))))
137
+
91
138
  (defn apply
92
139
  "Applies fn f to the argument list formed by prepending intervening
93
140
  arguments to args."
@@ -110,16 +157,30 @@
110
157
  "Return a new identifier with a unique name. If a prefix string is supplied,
111
158
  the name is prefix__# where # is some unique number. If prefix is not
112
159
  supplied, the prefix is 'g'."
113
- [[prefix "g"]]
160
+ [? (prefix "g")]
114
161
  (identifier (.gensym Apricot prefix)))
115
162
 
116
- (defn require
117
- "Require the given Ruby files. Works just like using Ruby's 'require' on
118
- each of the arguments."
119
- [& files]
163
+ (defn require-ruby
164
+ "Require the given Ruby files, skipping any which are already loaded. Just
165
+ like calling Ruby's require method on each argument."
166
+ [& names]
120
167
  ; (. Kernel require %) does not call the Rubygems custom require for some
121
168
  ; reason, so we use this method. (MAIN is the special toplevel object).
122
- (.each files | #(. MAIN send :require %)))
169
+ (.each names | #(. MAIN send :require %))
170
+ nil)
171
+
172
+ (defn load
173
+ "Load the given Apricot files, unconditionally. To skip loading files which
174
+ are already loaded, see 'require'."
175
+ [& names]
176
+ (.each names | #(Apricot/load %))
177
+ nil)
178
+
179
+ (defn require
180
+ "Load the given Apricot files, skipping any which are already loaded."
181
+ [& names]
182
+ (.each names | #(Apricot/require %))
183
+ nil)
123
184
 
124
185
  (defn str
125
186
  "With no args, return the empty string. With one arg x, return x converted
@@ -130,6 +191,8 @@
130
191
  ([x & args]
131
192
  (.reduce args (.apricot_str x) | #(.concat %1 (.apricot_str %2)))))
132
193
 
194
+ (def format Kernel/format)
195
+
133
196
  (defn print
134
197
  "Print the object(s) to standard output."
135
198
  [& args] (Kernel/print (apply str args)))
@@ -273,26 +336,6 @@
273
336
 
274
337
  ; Collection functions
275
338
 
276
- (defn seq
277
- "Returns a seq on the collection. If the collection is empty, returns nil.
278
- (seq nil) returns nil."
279
- [coll] (.to_seq coll))
280
-
281
- (defn first
282
- "Return the first item in the collection. Call seq on the argument. If coll
283
- is nil, return nil."
284
- [coll] (.first (seq coll)))
285
-
286
- (defn rest
287
- "Return a possibly empty seq of the items after the first. Call seq on the
288
- argument."
289
- [coll] (.rest (seq coll)))
290
-
291
- (defn next
292
- "Return a seq of the items after the first. Call seq on the argument. If
293
- there are no more items, return nil."
294
- [coll] (.next (seq coll)))
295
-
296
339
  (defn empty?
297
340
  "Return true if coll has no items - same as (not (seq coll)). Please use the
298
341
  idiom (seq x) rather than (not (empty? x))"
@@ -527,6 +570,8 @@
527
570
  (defn =
528
571
  "Return true if all of the arguments are equal, otherwise false. (=) returns
529
572
  true."
573
+ {:inline (fn [x y] `(. ~x == ~y))
574
+ :inline-arities #{2}}
530
575
  ([x] true)
531
576
  ([x y] (. x == y))
532
577
  ([x y & more]
@@ -545,7 +590,14 @@
545
590
  (defn compare
546
591
  "Return a negative number, zero, or a positive number when x is logically
547
592
  'less than', 'equal to', or 'greater than' y, respectively."
548
- [x y] (. x <=> y))
593
+ [x y]
594
+ (if (nil? x)
595
+ (if (nil? y)
596
+ 0
597
+ -1)
598
+ (if (nil? y)
599
+ 1
600
+ (. x <=> y))))
549
601
 
550
602
  (defn >
551
603
  "Return true if nums are in monotonically decreasing order, otherwise false."
@@ -774,7 +826,7 @@
774
826
 
775
827
  If test is true, evaluate then with var bound to the value of test,
776
828
  otherwise yield else."
777
- [bindings then [else nil]]
829
+ [bindings then ? (else nil)]
778
830
  `(let [temp# ~(bindings 1)]
779
831
  (if temp#
780
832
  (let [~(bindings 0) temp#]
@@ -839,6 +891,16 @@
839
891
 
840
892
  ; Miscellaneous (to be sorted)
841
893
 
894
+ (defn read
895
+ "Read the next object from io, which must be an instance of IO. The default
896
+ io is stdin."
897
+ [? (io STDIN)]
898
+ (.read_one (Apricot::Reader. io)))
899
+
900
+ (defn read-string
901
+ "Read one object from the string s."
902
+ [s] (read (StringIO. s)))
903
+
842
904
  (defn eval
843
905
  "Evaluate the form data structure (not text!) and return the result."
844
906
  [form] (Apricot::Compiler/eval_form form))
@@ -883,46 +945,33 @@
883
945
  [f]
884
946
  (let [m (meta f)]
885
947
  (println "-------------------------")
886
- (println (m :name))
887
- (println (m :arglists))
888
- (if (m :macro)
948
+ (println (:name m))
949
+ (println (:arglists m))
950
+ (if (:macro m)
889
951
  (println "Macro"))
890
- (println " " (m :doc))))
891
-
892
- ; REPL Utilities
893
-
894
- (defn decode
895
- "Print the Rubinius bytecode for the given Proc or Method."
896
- [f]
897
- (case f
898
- [Proc] (Kernel/puts (.. f block compiled_code decode))
899
- [Method] (Kernel/puts (.. f executable decode))
900
- (raise (str "Don't know how to decode " (.inspect f)))))
901
-
902
- (defmacro time
903
- "Evaluate the forms in body and return the time it took."
904
- [& body]
952
+ (println " " (:doc m))))
953
+
954
+ ; Namespaces
955
+
956
+ (defn refer
957
+ "For each public interned var in the namespace named by the symbol, adds a
958
+ mapping from the name of the var to the var to the current namespace."
959
+ [ns]
960
+ (each [var (.keys (.vars ns))]
961
+ (.add_alias *ns* var ns))
962
+ nil)
963
+
964
+ (defmacro ns
965
+ "Sets *ns* to the namespace named by name (unevaluated), creating it if
966
+ needed. Optionally takes a uses clause like (:use Foo Bar Baz) to important
967
+ vars from other namespaces."
968
+ [ns-sym ? (uses nil)]
905
969
  `(do
906
- (require "benchmark")
907
- (.realtime Benchmark | (fn [] ~@body))))
908
-
909
- (defmacro benchmark-ips
910
- "clause => [label form ...]
911
-
912
- Measure how many times per second each of the clause's bodies can be
913
- executed. Output is organized using the given label strings.
914
-
915
- This requires the benchmark-ips gem:
916
- gem install benchmark-ips"
917
- [& clauses]
918
- (let [bm (gensym)
919
- make-report (fn [clause]
920
- `(.report ~bm ~(first clause) | (fn [] ~@(rest clause))))
921
- reports (map make-report clauses)]
922
- `(do
923
- (try
924
- (require "benchmark/ips")
925
- (.ips Benchmark | (fn [~bm] ~@reports))
926
- (rescue [_ LoadError]
927
- (raise "benchmark-ips requires the benchmark-ips gem")))
928
- nil)))
970
+ (in-ns '~ns-sym)
971
+ ~(if (not= ns-sym 'Apricot::Core)
972
+ '(Apricot::Core/refer Apricot::Core))
973
+ ~@(if uses
974
+ (if (= :use (first uses))
975
+ (map (fn [ns] `(Apricot::Core/refer ~ns))
976
+ (rest uses))
977
+ (raise "Invalid clause in ns macro")))))
@@ -0,0 +1,37 @@
1
+ ; REPL Utilities
2
+
3
+ (defn decode
4
+ "Print the Rubinius bytecode for the given Proc or Method."
5
+ [f]
6
+ (case f
7
+ [Proc] (Kernel/puts (.. f block compiled_code decode))
8
+ [Method] (Kernel/puts (.. f executable decode))
9
+ (raise (str "Don't know how to decode " (.inspect f)))))
10
+
11
+ (defmacro time
12
+ "Evaluate the forms in body and return the time it took."
13
+ [& body]
14
+ `(do
15
+ (require-ruby "benchmark")
16
+ (.realtime Benchmark | (fn [] ~@body))))
17
+
18
+ (defmacro benchmark-ips
19
+ "clause => [label form ...]
20
+
21
+ Measure how many times per second each of the clause's bodies can be
22
+ executed. Output is organized using the given label strings.
23
+
24
+ This requires the benchmark-ips gem:
25
+ gem install benchmark-ips"
26
+ [& clauses]
27
+ (let [bm (gensym)
28
+ make-report (fn [clause]
29
+ `(.report ~bm ~(first clause) | (fn [] ~@(rest clause))))
30
+ reports (map make-report clauses)]
31
+ `(do
32
+ (try
33
+ (require-ruby "benchmark/ips")
34
+ (.ips Benchmark | (fn [~bm] ~@reports))
35
+ (rescue [_ LoadError]
36
+ (raise "benchmark-ips requires the benchmark-ips gem")))
37
+ nil)))
@@ -1,30 +1,11 @@
1
+ unless RUBY_VERSION.start_with?("2") && RUBY_ENGINE == "rbx" && Rubinius::VERSION.start_with?("2")
2
+ $stderr.puts "Apricot must be run on the stable Rubinius 2.0.0 release or newer."
3
+ exit 1
4
+ end
5
+
1
6
  require 'set'
2
7
 
3
8
  %w[
4
- version misc parser compiler ast macroexpand generator stages printers
5
- special_forms errors seq cons list identifier ruby_ext namespace
9
+ version misc namespace seq cons list identifier ruby_ext reader compiler
10
+ scopes variables macroexpand generator special_forms errors code_loader boot
6
11
  ].each {|r| require "apricot/#{r}" }
7
-
8
- # Start "booting" apricot. Set up core namespace and load the core library.
9
- module Apricot
10
- Core = Namespace.new
11
-
12
- Core.set_var(:"*ns*", Core)
13
-
14
- Core.set_var(:"in-ns", lambda do |constant|
15
- Apricot.current_namespace = Namespace.find_or_create constant
16
- end)
17
-
18
- Core.set_var(:ns, lambda do |constant|
19
- List[Identifier.intern(:"in-ns"),
20
- List[Identifier.intern(:quote), constant]]
21
- end)
22
- Core.get_var(:ns).apricot_meta = {macro: true}
23
-
24
- # TODO: add and use a proper code loader
25
- Apricot::Compiler.compile(File.expand_path('../../kernel/core.apr', __FILE__))
26
-
27
- # ::User = Namespace.new
28
- Apricot.current_namespace = Core
29
- # TODO: make Apricot::Core public vars visible in User, default to User
30
- end