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
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
|