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.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/.ruby-version +1 -1
- data/.travis.yml +1 -0
- data/Gemfile.lock +229 -11
- data/README.md +46 -29
- data/Rakefile +1 -1
- data/apricot.gemspec +7 -3
- data/benchmarks/factorial.rb +51 -0
- data/benchmarks/interpolate.rb +20 -0
- data/bin/apricot +5 -23
- data/examples/bot.apr +1 -4
- data/examples/cinch-bot.apr +3 -3
- data/examples/sinatra.apr +9 -0
- data/kernel/core.apr +124 -75
- data/kernel/repl.apr +37 -0
- data/lib/apricot.rb +7 -26
- data/lib/apricot/boot.rb +24 -0
- data/lib/apricot/code_loader.rb +108 -0
- data/lib/apricot/compiler.rb +265 -32
- data/lib/apricot/generator.rb +10 -3
- data/lib/apricot/identifier.rb +25 -10
- data/lib/apricot/list.rb +28 -41
- data/lib/apricot/macroexpand.rb +14 -8
- data/lib/apricot/misc.rb +2 -1
- data/lib/apricot/namespace.rb +20 -3
- data/lib/apricot/{parser.rb → reader.rb} +221 -194
- data/lib/apricot/repl.rb +67 -24
- data/lib/apricot/ruby_ext.rb +27 -16
- data/lib/apricot/scopes.rb +159 -0
- data/lib/apricot/seq.rb +43 -1
- data/lib/apricot/special_forms.rb +16 -695
- data/lib/apricot/special_forms/def.rb +32 -0
- data/lib/apricot/special_forms/do.rb +23 -0
- data/lib/apricot/special_forms/dot.rb +112 -0
- data/lib/apricot/special_forms/fn.rb +342 -0
- data/lib/apricot/special_forms/if.rb +31 -0
- data/lib/apricot/special_forms/let.rb +8 -0
- data/lib/apricot/special_forms/loop.rb +10 -0
- data/lib/apricot/special_forms/quote.rb +9 -0
- data/lib/apricot/special_forms/recur.rb +26 -0
- data/lib/apricot/special_forms/try.rb +146 -0
- data/lib/apricot/variables.rb +65 -0
- data/lib/apricot/version.rb +1 -1
- data/spec/compiler_spec.rb +53 -450
- data/spec/fn_spec.rb +206 -0
- data/spec/list_spec.rb +1 -1
- data/spec/reader_spec.rb +349 -0
- data/spec/spec_helper.rb +40 -4
- data/spec/special_forms_spec.rb +203 -0
- metadata +99 -133
- data/lib/apricot/ast.rb +0 -3
- data/lib/apricot/ast/identifier.rb +0 -111
- data/lib/apricot/ast/list.rb +0 -99
- data/lib/apricot/ast/literals.rb +0 -240
- data/lib/apricot/ast/node.rb +0 -45
- data/lib/apricot/ast/scopes.rb +0 -147
- data/lib/apricot/ast/toplevel.rb +0 -66
- data/lib/apricot/ast/variables.rb +0 -64
- data/lib/apricot/printers.rb +0 -12
- data/lib/apricot/stages.rb +0 -60
- 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
|
data/bin/apricot
CHANGED
@@ -1,20 +1,7 @@
|
|
1
|
-
#!/usr/bin/env
|
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
|
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
|
34
|
+
Apricot::Compiler.eval(args.first, "(eval)", 1)
|
52
35
|
when :stdin
|
53
|
-
Apricot::Compiler.eval(STDIN.read, "(stdin)", 1
|
36
|
+
Apricot::Compiler.eval(STDIN.read, "(stdin)", 1)
|
54
37
|
when :file
|
55
|
-
|
56
|
-
Apricot::Compiler.compile(args.first, nil, bytecode)
|
38
|
+
Apricot::CodeLoader.require(File.expand_path(args.first))
|
57
39
|
end
|
58
40
|
end
|
data/examples/bot.apr
CHANGED
data/examples/cinch-bot.apr
CHANGED
@@ -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.
|
5
|
+
(.server= "irc.freenode.net")
|
6
6
|
(.nick= "apribot")
|
7
|
-
(.channels= ["#
|
7
|
+
(.channels= ["#apricot"])))
|
8
8
|
|
9
9
|
(.on :message #r/^apribot[:,]?\s+(.+)/
|
10
10
|
| (fn [m msg] (.reply m msg true)))
|
data/kernel/core.apr
CHANGED
@@ -25,10 +25,10 @@
|
|
25
25
|
{})
|
26
26
|
arglists (if (.is_a? (.first body) Array)
|
27
27
|
(list (.first body))
|
28
|
-
(.
|
29
|
-
(.map body | #(if (.is_a? % Apricot::
|
28
|
+
(.to_seq
|
29
|
+
(.map body | #(if (.is_a? % Apricot::Seq)
|
30
30
|
(.first %)))))
|
31
|
-
f (
|
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 (
|
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
|
-
"
|
75
|
-
[
|
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
|
-
[
|
160
|
+
[? (prefix "g")]
|
114
161
|
(identifier (.gensym Apricot prefix)))
|
115
162
|
|
116
|
-
(defn require
|
117
|
-
"Require the given Ruby files
|
118
|
-
|
119
|
-
[&
|
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
|
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]
|
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
|
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 (
|
887
|
-
(println (
|
888
|
-
(if (
|
948
|
+
(println (:name m))
|
949
|
+
(println (:arglists m))
|
950
|
+
(if (:macro m)
|
889
951
|
(println "Macro"))
|
890
|
-
(println " " (
|
891
|
-
|
892
|
-
;
|
893
|
-
|
894
|
-
(defn
|
895
|
-
"
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
|
902
|
-
(defmacro
|
903
|
-
"
|
904
|
-
|
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
|
-
(
|
907
|
-
(
|
908
|
-
|
909
|
-
(
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
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")))))
|
data/kernel/repl.apr
ADDED
@@ -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)))
|
data/lib/apricot.rb
CHANGED
@@ -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
|
5
|
-
|
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
|