apricot 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/spec/fn_spec.rb
ADDED
@@ -0,0 +1,206 @@
|
|
1
|
+
describe 'Apricot' do
|
2
|
+
include CompilerSpec
|
3
|
+
|
4
|
+
it 'compiles fn forms' do
|
5
|
+
apr('((fn []))').should == nil
|
6
|
+
apr('((fn [] 42))').should == 42
|
7
|
+
apr('((fn [x] x) 42)').should == 42
|
8
|
+
apr('((fn [x y] [y x]) 1 2)').should == [2, 1]
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'compiles fn forms with optional arguments' do
|
12
|
+
apr('((fn [? (x 42)] x))').should == 42
|
13
|
+
apr('((fn [? (x 42)] x) 0)').should == 0
|
14
|
+
apr('((fn [x ? (y 2)] [x y]) 1)').should == [1, 2]
|
15
|
+
apr('((fn [x ? (y 2)] [x y]) 3 4)').should == [3, 4]
|
16
|
+
apr('((fn [? (x 1) (y 2)] [x y]))').should == [1, 2]
|
17
|
+
apr('((fn [? (x 1) (y 2)] [x y]) 3)').should == [3, 2]
|
18
|
+
apr('((fn [? (x 1) (y 2)] [x y]) 3 4)').should == [3, 4]
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'compiles fn forms with splat arguments' do
|
22
|
+
apr('((fn [& x] x))').should == []
|
23
|
+
apr('((fn [& x] x) 1)').should == [1]
|
24
|
+
apr('((fn [& x] x) 1 2)').should == [1, 2]
|
25
|
+
apr('((fn [x & y] y) 1)').should == []
|
26
|
+
apr('((fn [x & y] y) 1 2 3)').should == [2, 3]
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'compiles fn forms with optional and splat arguments' do
|
30
|
+
apr('((fn [x ? (y 2) & z] [x y z]) 1)').should == [1, 2, []]
|
31
|
+
apr('((fn [x ? (y 2) & z] [x y z]) 1 3)').should == [1, 3, []]
|
32
|
+
apr('((fn [x ? (y 2) & z] [x y z]) 1 3 4 5)').should == [1, 3, [4, 5]]
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'compiles fn forms with block arguments' do
|
36
|
+
apr('((fn [| block] block))').should == nil
|
37
|
+
apr('(.call (fn [| block] (block)) | (fn [] 42))').should == 42
|
38
|
+
|
39
|
+
fn = apr '(fn [x | block] (block x))'
|
40
|
+
# Without passing a block, 'block' is nil.
|
41
|
+
expect { fn.call(2) }.to raise_error(NoMethodError)
|
42
|
+
fn.call(2) {|x| x + 40 }.should == 42
|
43
|
+
|
44
|
+
reduce_args = apr <<-CODE
|
45
|
+
(fn reduce-args
|
46
|
+
([x] x)
|
47
|
+
([x y | f] (f x y))
|
48
|
+
([x y & more | f]
|
49
|
+
(if (seq more)
|
50
|
+
(recur (f x y) (first more) (next more) f)
|
51
|
+
(f x y))))
|
52
|
+
CODE
|
53
|
+
|
54
|
+
reduce_args.call(1).should == 1
|
55
|
+
reduce_args.call(40, 2) {|x,y| x * y }.should == 80
|
56
|
+
reduce_args.call(1,2,3,4,5,6) {|x,y| x + y }.should == 21
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'does not compile invalid fn forms' do
|
60
|
+
bad_apr '(fn :foo)'
|
61
|
+
bad_apr '(fn [1])'
|
62
|
+
bad_apr '(fn [?])'
|
63
|
+
bad_apr '(fn [? (x 1) y])'
|
64
|
+
bad_apr '(fn [? (1 1)])'
|
65
|
+
bad_apr '(fn [? (x)])'
|
66
|
+
bad_apr '(fn [&])'
|
67
|
+
bad_apr '(fn [? &])'
|
68
|
+
bad_apr '(fn [& ?])'
|
69
|
+
bad_apr '(fn [& rest ? (opt 1)])'
|
70
|
+
bad_apr '(fn [& x y])'
|
71
|
+
bad_apr '(fn [x x])'
|
72
|
+
bad_apr '(fn [x & rest1 & rest2])'
|
73
|
+
bad_apr '(fn [a b x c d x e f])'
|
74
|
+
bad_apr '(fn [a x b ? (x 1)])'
|
75
|
+
bad_apr '(fn [a b x c d & x])'
|
76
|
+
bad_apr '(fn [a b c ? (x 1) (y 2) (x 3)])'
|
77
|
+
bad_apr '(fn [a b ? (x 1) & x])'
|
78
|
+
bad_apr '(fn [|])'
|
79
|
+
bad_apr '(fn [? |])'
|
80
|
+
bad_apr '(fn [| ?])'
|
81
|
+
bad_apr '(fn [| block ? (opt 1)])'
|
82
|
+
bad_apr '(fn [| &])'
|
83
|
+
bad_apr '(fn [| & a])'
|
84
|
+
bad_apr '(fn [| a &])'
|
85
|
+
bad_apr '(fn [& x |])'
|
86
|
+
bad_apr '(fn [| x y])'
|
87
|
+
bad_apr '(fn [| x & y])'
|
88
|
+
bad_apr '(fn [x | x])'
|
89
|
+
bad_apr '(fn [x | b1 | b2])'
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'compiles arity-overloaded fn forms' do
|
93
|
+
apr('((fn ([] 0)))').should == 0
|
94
|
+
apr('((fn ([x] x)) 42)').should == 42
|
95
|
+
apr('((fn ([? (x 42)] x)))').should == 42
|
96
|
+
apr('((fn ([& rest] rest)) 1 2 3)').should == [1, 2, 3]
|
97
|
+
apr('((fn ([] 0) ([x] x)))').should == 0
|
98
|
+
apr('((fn ([] 0) ([x] x)) 42)').should == 42
|
99
|
+
apr('((fn ([x] x) ([x y] y)) 42)').should == 42
|
100
|
+
apr('((fn ([x] x) ([x y] y)) 42 13)').should == 13
|
101
|
+
apr('((fn ([x] x) ([x y & z] z)) 1 2 3 4)').should == [3, 4]
|
102
|
+
|
103
|
+
add_fn = apr <<-CODE
|
104
|
+
(fn
|
105
|
+
([] 0)
|
106
|
+
([x] x)
|
107
|
+
([x y] (.+ x y))
|
108
|
+
([x y & more]
|
109
|
+
(.reduce more (.+ x y) :+)))
|
110
|
+
CODE
|
111
|
+
|
112
|
+
add_fn.call.should == 0
|
113
|
+
add_fn.call(42).should == 42
|
114
|
+
add_fn.call(1,2).should == 3
|
115
|
+
add_fn.call(1,2,3).should == 6
|
116
|
+
add_fn.call(1,2,3,4,5,6,7,8).should == 36
|
117
|
+
|
118
|
+
two_or_three = apr '(fn ([x y] 2) ([x y z] 3))'
|
119
|
+
expect { two_or_three.call }.to raise_error(ArgumentError)
|
120
|
+
expect { two_or_three.call(1) }.to raise_error(ArgumentError)
|
121
|
+
two_or_three.call(1,2).should == 2
|
122
|
+
two_or_three.call(1,2,3).should == 3
|
123
|
+
expect { two_or_three.call(1,2,3,4) }.to raise_error(ArgumentError)
|
124
|
+
expect { two_or_three.call(1,2,3,4,5) }.to raise_error(ArgumentError)
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'compiles arity-overloaded fns with no matching overloads for some arities' do
|
128
|
+
zero_or_two = apr '(fn ([] 0) ([x y] 2))'
|
129
|
+
zero_or_two.call.should == 0
|
130
|
+
expect { zero_or_two.call(1) }.to raise_error(ArgumentError)
|
131
|
+
zero_or_two.call(1,2).should == 2
|
132
|
+
expect { zero_or_two.call(1,2,3) }.to raise_error(ArgumentError)
|
133
|
+
|
134
|
+
one_or_four = apr '(fn ([w] 1) ([w x y z] 4))'
|
135
|
+
expect { one_or_four.call }.to raise_error(ArgumentError)
|
136
|
+
one_or_four.call(1).should == 1
|
137
|
+
expect { one_or_four.call(1,2) }.to raise_error(ArgumentError)
|
138
|
+
expect { one_or_four.call(1,2,3) }.to raise_error(ArgumentError)
|
139
|
+
one_or_four.call(1,2,3,4).should == 4
|
140
|
+
expect { one_or_four.call(1,2,3,4,5) }.to raise_error(ArgumentError)
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'does not compile invalid arity-overloaded fn forms' do
|
144
|
+
bad_apr '(fn ([] 1) :foo)'
|
145
|
+
bad_apr '(fn ([] 1) ([] 2))'
|
146
|
+
bad_apr '(fn ([? (o 1)] 1) ([] 2))'
|
147
|
+
bad_apr '(fn ([] 1) ([? (o 2)] 2))'
|
148
|
+
bad_apr '(fn ([? (o 1)] 1) ([? (o 2)] 2))'
|
149
|
+
bad_apr '(fn ([x ? (o 1)] 1) ([x] 2))'
|
150
|
+
bad_apr '(fn ([x ? (o 1)] 1) ([? (o 2)] 2))'
|
151
|
+
bad_apr '(fn ([x y z ? (o 1)] 1) ([x y z & rest] 2))'
|
152
|
+
bad_apr '(fn ([x ? (o 1) (p 2) (q 3)] 1) ([x y z] 2))'
|
153
|
+
bad_apr '(fn ([x & rest] 1) ([x y] 2))'
|
154
|
+
bad_apr '(fn ([x & rest] 1) ([x ? (o 1)] 2))'
|
155
|
+
bad_apr '(fn ([x ? (o 1) & rest] 1) ([x] 2))'
|
156
|
+
bad_apr '(fn ([? (x 1) (y 2)] 3) ([x & y] 4))'
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'compiles fn forms with self-reference' do
|
160
|
+
foo = apr '(fn foo [] foo)'
|
161
|
+
foo.call.should == foo
|
162
|
+
|
163
|
+
# This one will stack overflow from the infinite loop.
|
164
|
+
expect { apr '((fn foo [] (foo)))' }.to raise_error(SystemStackError)
|
165
|
+
|
166
|
+
add = apr <<-CODE
|
167
|
+
(fn add
|
168
|
+
([] 0)
|
169
|
+
([& args]
|
170
|
+
(.+ (first args) (apply add (rest args)))))
|
171
|
+
CODE
|
172
|
+
|
173
|
+
add.call.should == 0
|
174
|
+
add.call(1).should == 1
|
175
|
+
add.call(1,2,3).should == 6
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'compiles recur forms in fns' do
|
179
|
+
apr(<<-CODE).should == 15
|
180
|
+
((fn [x y]
|
181
|
+
(if (. x > 0)
|
182
|
+
(recur (. x - 1) (. y + x))
|
183
|
+
y))
|
184
|
+
5 0)
|
185
|
+
CODE
|
186
|
+
end
|
187
|
+
|
188
|
+
it 'compiles recur forms in fns with optional arguments' do
|
189
|
+
apr(<<-CODE).should == 150
|
190
|
+
((fn [x y ? (mult 10)]
|
191
|
+
(if (. x > 0)
|
192
|
+
(recur (. x - 1) (. y + x) mult)
|
193
|
+
(* y mult)))
|
194
|
+
5 0)
|
195
|
+
CODE
|
196
|
+
|
197
|
+
apr(<<-CODE).should == 300
|
198
|
+
((fn [x y ? (mult 10)]
|
199
|
+
(if (. x > 0)
|
200
|
+
(recur (. x - 1) (. y + x) mult)
|
201
|
+
(* y mult)))
|
202
|
+
5 0 20)
|
203
|
+
CODE
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
data/spec/list_spec.rb
CHANGED
data/spec/reader_spec.rb
ADDED
@@ -0,0 +1,349 @@
|
|
1
|
+
describe Apricot::Reader do
|
2
|
+
def read(s)
|
3
|
+
@forms = described_class.read_string(s, "(spec)")
|
4
|
+
@first = @forms.first
|
5
|
+
@forms
|
6
|
+
end
|
7
|
+
|
8
|
+
def read_one(s, klass = nil)
|
9
|
+
read(s).length.should == 1
|
10
|
+
@first.should be_a(klass) if klass
|
11
|
+
@first
|
12
|
+
end
|
13
|
+
|
14
|
+
def expect_syntax_error(s)
|
15
|
+
expect { read(s) }.to raise_error(Apricot::SyntaxError)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'reads nothing' do
|
19
|
+
read('').should be_empty
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'skips whitespace' do
|
23
|
+
read(" \n\t,").should be_empty
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'skips comments' do
|
27
|
+
read('; example').should be_empty
|
28
|
+
read('#!/usr/bin/env apricot').should be_empty
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'discards commented forms' do
|
32
|
+
read('#_form').should be_empty
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'reads identifiers' do
|
36
|
+
read_one('example', Identifier)
|
37
|
+
@first.name.should == :example
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'reads pipe identifiers' do
|
41
|
+
read_one('#|example|').should == Identifier.intern(:example)
|
42
|
+
read_one('#|foo bar|').should == Identifier.intern(:"foo bar")
|
43
|
+
read_one('#|foo\nbar|')
|
44
|
+
@first.should == Identifier.intern(:"foo\nbar")
|
45
|
+
read_one('#|foo\|bar|').should == Identifier.intern(:"foo|bar")
|
46
|
+
read_one('#|foo"bar|').should == Identifier.intern(:'foo"bar')
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'does not read incomplete pipe identifiers' do
|
50
|
+
expect_syntax_error '#|foo'
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'reads constants' do
|
54
|
+
read_one('Example', Identifier)
|
55
|
+
@first.constant?.should be_true
|
56
|
+
@first.const_names.should == [:Example]
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'reads invalid constants as identifiers' do
|
60
|
+
read_one('Fo$o', Identifier)
|
61
|
+
@first.constant?.should be_false
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'reads scoped constants' do
|
65
|
+
read_one('Foo::Bar::Baz', Identifier)
|
66
|
+
@first.constant?.should be_true
|
67
|
+
@first.const_names.should == [:Foo, :Bar, :Baz]
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'reads invalid scoped constants as identifiers' do
|
71
|
+
read_one('Foo::', Identifier)
|
72
|
+
@first.constant?.should be_false
|
73
|
+
read_one('Foo:', Identifier)
|
74
|
+
@first.constant?.should be_false
|
75
|
+
read_one('Foo::a', Identifier)
|
76
|
+
@first.constant?.should be_false
|
77
|
+
read_one('Foo::::Bar', Identifier)
|
78
|
+
@first.constant?.should be_false
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'reads true, false, nil, and self' do
|
82
|
+
read('true false nil self').length.should == 4
|
83
|
+
@forms[0].should == true
|
84
|
+
@forms[1].should == false
|
85
|
+
@forms[2].should == nil
|
86
|
+
@forms[3].should == Identifier.intern(:self)
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'reads fixnums' do
|
90
|
+
read_one('123').should == 123
|
91
|
+
read_one('-123').should == -123
|
92
|
+
read_one('+123').should == 123
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'reads bignums' do
|
96
|
+
read_one('12345678901234567890').should == 12345678901234567890
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'reads radix integers' do
|
100
|
+
read_one('2r10').should == 2
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'reads floats' do
|
104
|
+
read_one('1.23').should == 1.23
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'reads rationals' do
|
108
|
+
read_one('12/34').should == Rational(12, 34)
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'does not read invalid numbers' do
|
112
|
+
expect_syntax_error '12abc'
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'reads empty strings' do
|
116
|
+
read_one('""', String).should == ''
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'reads strings' do
|
120
|
+
read_one('"Hello, world!"').should == 'Hello, world!'
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'reads multiline strings' do
|
124
|
+
read_one(%{"This is\na test"}).should == "This is\na test"
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'does not read unfinished strings' do
|
128
|
+
expect_syntax_error '"'
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'reads strings with character escapes' do
|
132
|
+
read_one('"\\a\\b\\t\\n\\v\\f\\r\\e\\"\\\\"').should == "\a\b\t\n\v\f\r\e\"\\"
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'reads strings with octal escapes' do
|
136
|
+
read_one('"\\1\\01\\001"').should == "\001\001\001"
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'reads strings with hex escapes' do
|
140
|
+
read_one('"\\x1\\x01"').should == "\001\001"
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'does not read strings with invalid hex escapes' do
|
144
|
+
expect_syntax_error '"\\x"'
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'stops parsing hex/octal escapes in strings at non-hex/octal digits' do
|
148
|
+
read_one('"\xAZ\082"').should == "\x0AZ\00082"
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'reads #q quotation strings' do
|
152
|
+
read_one('#q{foo}').should == 'foo'
|
153
|
+
read_one('#q{\n}').should == '\n'
|
154
|
+
read_one('#Q{\n}').should == "\n"
|
155
|
+
read_one('#q{\\\\}').should == '\\'
|
156
|
+
read_one('#q{\\}}').should == '}'
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'does not read incomplete #q quotation strings' do
|
160
|
+
expect_syntax_error '#q{'
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'reads regexes' do
|
164
|
+
read_one('#r!!').should == //
|
165
|
+
read_one('#r!egex!').should == /egex/
|
166
|
+
read_one('#r(egex)').should == /egex/
|
167
|
+
read_one('#r[egex]').should == /egex/
|
168
|
+
read_one('#r{egex}').should == /egex/
|
169
|
+
read_one('#r<egex>').should == /egex/
|
170
|
+
read_one('#r!\!!').should == /!/
|
171
|
+
read_one('#r!foo\bar!').should == /foo\bar/
|
172
|
+
read_one('#r!\\\\!').should == /\\/
|
173
|
+
end
|
174
|
+
|
175
|
+
it 'reads regexes with trailing options' do
|
176
|
+
read_one('#r//i', Regexp)
|
177
|
+
@first.options.should == Regexp::IGNORECASE
|
178
|
+
read_one('#r/foo/x', Regexp)
|
179
|
+
@first.options.should == Regexp::EXTENDED
|
180
|
+
read_one('#r//im', Regexp)
|
181
|
+
@first.options.should == Regexp::IGNORECASE | Regexp::MULTILINE
|
182
|
+
end
|
183
|
+
|
184
|
+
it 'does not read regexes with unknown trailing options' do
|
185
|
+
expect_syntax_error '#r/foo/asdf'
|
186
|
+
end
|
187
|
+
|
188
|
+
it 'does not read incomplete regexes' do
|
189
|
+
expect_syntax_error '#r/foo'
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'reads symbols' do
|
193
|
+
read_one(':example').should == :example
|
194
|
+
end
|
195
|
+
|
196
|
+
it 'reads quoted symbols' do
|
197
|
+
read_one(':"\x01()"').should == :"\x01()"
|
198
|
+
end
|
199
|
+
|
200
|
+
it 'does not read unfinished quoted symbols' do
|
201
|
+
expect_syntax_error ':"'
|
202
|
+
end
|
203
|
+
|
204
|
+
it 'does not read empty symbols' do
|
205
|
+
expect_syntax_error ':'
|
206
|
+
end
|
207
|
+
|
208
|
+
it 'does read empty quoted symbols' do
|
209
|
+
read_one(':""').should == :""
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'reads empty lists' do
|
213
|
+
read_one('()').should == List[]
|
214
|
+
end
|
215
|
+
|
216
|
+
it 'reads lists' do
|
217
|
+
read_one('(1 two)').should == List[1, Identifier.intern(:two)]
|
218
|
+
end
|
219
|
+
|
220
|
+
it 'reads empty arrays' do
|
221
|
+
read_one('[]').should == []
|
222
|
+
end
|
223
|
+
|
224
|
+
it 'reads arrays' do
|
225
|
+
read_one('[1 two]').should == [1, Identifier.intern(:two)]
|
226
|
+
end
|
227
|
+
|
228
|
+
it 'reads empty hashes' do
|
229
|
+
read_one('{}').should == {}
|
230
|
+
end
|
231
|
+
|
232
|
+
it 'reads hashes' do
|
233
|
+
read_one('{:example 1}').should == {example: 1}
|
234
|
+
end
|
235
|
+
|
236
|
+
it 'does not read invalid hashes' do
|
237
|
+
expect_syntax_error '{:foo 1 :bar}'
|
238
|
+
end
|
239
|
+
|
240
|
+
it 'reads empty sets' do
|
241
|
+
read_one('#{}').should == Set[]
|
242
|
+
end
|
243
|
+
|
244
|
+
it 'reads sets' do
|
245
|
+
read_one('#{1 two}').should == Set[1, Identifier.intern(:two)]
|
246
|
+
end
|
247
|
+
|
248
|
+
it 'does not read incomplete structures' do
|
249
|
+
expect_syntax_error '('
|
250
|
+
expect_syntax_error '['
|
251
|
+
expect_syntax_error '{'
|
252
|
+
expect_syntax_error '#{'
|
253
|
+
end
|
254
|
+
|
255
|
+
it 'reads multiple forms' do
|
256
|
+
read('foo bar').length.should == 2
|
257
|
+
@forms[0].should == Identifier.intern(:foo)
|
258
|
+
@forms[1].should == Identifier.intern(:bar)
|
259
|
+
end
|
260
|
+
|
261
|
+
it 'reads quoted forms' do
|
262
|
+
read_one("'test").should == List[Identifier.intern(:quote), Identifier.intern(:test)]
|
263
|
+
end
|
264
|
+
|
265
|
+
it 'reads syntax quoted forms' do
|
266
|
+
read_one('`1').should == 1
|
267
|
+
read_one('`~1').should == 1
|
268
|
+
|
269
|
+
apply = Identifier.intern(:apply)
|
270
|
+
concat = Identifier.intern(:concat)
|
271
|
+
list = Identifier.intern(:list)
|
272
|
+
quote = Identifier.intern(:quote)
|
273
|
+
|
274
|
+
begin
|
275
|
+
old_gensym = Apricot.instance_variable_get :@gensym
|
276
|
+
Apricot.instance_variable_set :@gensym, 41
|
277
|
+
|
278
|
+
read_one("`(foo ~bar ~@baz quux#)").should ==
|
279
|
+
List[concat,
|
280
|
+
List[list,
|
281
|
+
List[quote,
|
282
|
+
Identifier.intern(:foo)]],
|
283
|
+
List[list,
|
284
|
+
Identifier.intern(:bar)],
|
285
|
+
Identifier.intern(:baz),
|
286
|
+
List[list,
|
287
|
+
List[quote,
|
288
|
+
Identifier.intern(:'quux#__42')]]]
|
289
|
+
ensure
|
290
|
+
Apricot.instance_variable_set :@gensym, old_gensym
|
291
|
+
end
|
292
|
+
|
293
|
+
read_one('`[~a]').should ==
|
294
|
+
List[apply,
|
295
|
+
Identifier.intern(:array),
|
296
|
+
List[concat,
|
297
|
+
List[list,
|
298
|
+
Identifier.intern(:a)]]]
|
299
|
+
|
300
|
+
read_one('`{:a ~b}').should ==
|
301
|
+
List[apply,
|
302
|
+
Identifier.intern(:hash),
|
303
|
+
List[concat,
|
304
|
+
List[list,
|
305
|
+
:a],
|
306
|
+
List[list,
|
307
|
+
Identifier.intern(:b)]]]
|
308
|
+
|
309
|
+
read_one('`#{~a}').should ==
|
310
|
+
List[apply,
|
311
|
+
Identifier.intern(:'hash-set'),
|
312
|
+
List[concat,
|
313
|
+
List[list,
|
314
|
+
Identifier.intern(:a)]]]
|
315
|
+
end
|
316
|
+
|
317
|
+
it 'does not read invalid unquote forms' do
|
318
|
+
expect_syntax_error '`~@a'
|
319
|
+
end
|
320
|
+
|
321
|
+
it 'does not read incomplete unquote forms' do
|
322
|
+
expect_syntax_error '~'
|
323
|
+
expect_syntax_error '~@'
|
324
|
+
end
|
325
|
+
|
326
|
+
it 'reads #() shorthand' do
|
327
|
+
ids = (:a..:z).map {|sym| Identifier.intern(sym) }
|
328
|
+
Apricot.stub(:gensym).and_return(*ids)
|
329
|
+
|
330
|
+
read("#()").should == read("(fn [] ())")
|
331
|
+
read("#(foo)").should == read("(fn [] (foo))")
|
332
|
+
read("#(%)").should == read("(fn [a] (a))")
|
333
|
+
read("#(% %2)").should == read("(fn [b c] (b c))")
|
334
|
+
read("#(%1 %2)").should == read("(fn [d e] (d e))")
|
335
|
+
read("#(%2)").should == read("(fn [g f] (f))")
|
336
|
+
read("#(%&)").should == read("(fn [& h] (h))")
|
337
|
+
read("#(% %&)").should == read("(fn [i & j] (i j))")
|
338
|
+
|
339
|
+
expect_syntax_error("#(%0)")
|
340
|
+
expect_syntax_error("#(%-1)")
|
341
|
+
expect_syntax_error("#(%x)")
|
342
|
+
expect_syntax_error("#(%1.1)")
|
343
|
+
expect_syntax_error("#(%1asdf)")
|
344
|
+
end
|
345
|
+
|
346
|
+
it 'does not read invalid reader macros' do
|
347
|
+
expect_syntax_error('#x')
|
348
|
+
end
|
349
|
+
end
|