edn 1.0.3 → 1.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +94 -21
- data/Rakefile +5 -0
- data/edn.gemspec +4 -4
- data/lib/edn.rb +4 -20
- data/lib/edn/char_stream.rb +84 -0
- data/lib/edn/parser.rb +333 -201
- data/lib/edn/reader.rb +11 -24
- data/lib/edn/version.rb +1 -1
- data/spec/edn/char_stream_spec.rb +111 -0
- data/spec/edn/parser_spec.rb +31 -79
- data/spec/edn/reader_spec.rb +9 -9
- data/spec/edn_spec.rb +39 -5
- data/spec/spec_helper.rb +16 -4
- metadata +11 -26
- data/lib/edn/string_transformer.rb +0 -46
- data/lib/edn/transform.rb +0 -59
- data/lib/parslet/ignore.rb +0 -24
- data/spec/edn/transform_spec.rb +0 -176
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 972dc5d81c03165a6a51a6e3ea561f63dc38afaa
|
4
|
+
data.tar.gz: 8e55935d4bd465c787920bddd99083db37ab5d07
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f7902d652632b1553a746a62a54e6d602281f6f1d6f45f50e9234ba9e2c04321fa8a86d5e587f141ddd8ad0f90cb2b54604adfa11aac9c174716a45d3f061d56
|
7
|
+
data.tar.gz: 247a8609a0d39f19165640e659351444fedbeebfdb518e7c9eeb055c9225f59aac309d1441736cea7bbf2a509bde5684a997a2e0624c56e669d1ce1454132397
|
data/README.md
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
|
6
6
|
© 2012 Relevance Inc
|
7
7
|
|
8
|
-
**edn-ruby** is a Ruby library to read and write
|
8
|
+
**edn-ruby** is a Ruby library to read and write EDN (extensible data notation), a subset of Clojure used for transferring data between applications, much like JSON, YAML, or XML.
|
9
9
|
|
10
10
|
## Installation
|
11
11
|
|
@@ -23,30 +23,68 @@ Or install it yourself as:
|
|
23
23
|
|
24
24
|
## Usage
|
25
25
|
|
26
|
-
To read a string of
|
26
|
+
To read a string of EDN:
|
27
27
|
|
28
28
|
```ruby
|
29
29
|
EDN.read('[1 2 {:foo "bar"}]')
|
30
30
|
```
|
31
31
|
|
32
|
-
|
32
|
+
Alternatively you can pass in an IO instance, for
|
33
|
+
example an open file:
|
33
34
|
|
34
35
|
```ruby
|
35
|
-
data.
|
36
|
+
File.open("data.edn") do |f|
|
37
|
+
data = EDN.read(f)
|
38
|
+
# Do something with data
|
39
|
+
end
|
36
40
|
```
|
37
41
|
|
38
|
-
By default
|
42
|
+
By default EDN.read will throw an execption
|
43
|
+
if you try to read past the end of the data:
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
EDN.read("") # Boom!
|
47
|
+
```
|
48
|
+
|
49
|
+
Alternatively, the `EDN.read` method takes an optional
|
50
|
+
parameter, which is the value to return
|
51
|
+
when it hits the end of data:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
EDN.read("", :nomore)
|
39
55
|
|
40
|
-
|
56
|
+
#=> :nomore
|
57
|
+
```
|
58
|
+
|
59
|
+
There is no problem using `nil` as an eof value.
|
60
|
+
|
61
|
+
### EDN::Reader
|
41
62
|
|
42
|
-
|
63
|
+
You can also do things in a more object oriented way by
|
64
|
+
creating instances of `EDN::Reader`:
|
43
65
|
|
44
66
|
```ruby
|
45
67
|
r = EDN::Reader.new('[1 2 3] {:a 1 :b 2}')
|
46
68
|
|
47
69
|
r.read #=> [1, 2, 3]
|
48
70
|
r.read #=> {:a => 1, :b => 2}
|
49
|
-
r.read #=> RuntimeError:
|
71
|
+
r.read #=> RuntimeError: Unexpected end of file
|
72
|
+
```
|
73
|
+
|
74
|
+
`EDN:Reader` will also take an IO instance:
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
r = EDN::Reader.new(open("data.edn"))
|
78
|
+
|
79
|
+
r.read # Read the first form from the file.
|
80
|
+
r.read # Read the second form from the file.
|
81
|
+
r.read # Read the third from from the file.
|
82
|
+
```
|
83
|
+
|
84
|
+
You can also iterate through the forms with `each`:
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
r = EDN::Reader.new('[1 2 3] {:a 1 :b 2}')
|
50
88
|
|
51
89
|
r.each do |form|
|
52
90
|
p form
|
@@ -54,35 +92,69 @@ end
|
|
54
92
|
|
55
93
|
#=> [1, 2, 3]
|
56
94
|
#=> {:a => 1, :b => 2}
|
95
|
+
```
|
57
96
|
|
58
|
-
|
97
|
+
Note that in contrast to earlier versions of this gem,
|
98
|
+
EDN::Reader is no longer `Enumerable`.
|
99
|
+
|
100
|
+
Like `EDN.read`, `Reader.read` also takes an optional
|
101
|
+
parameter, which is returned when there is no more data:
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
r = EDN::Reader.new('1 2 3')
|
105
|
+
r.read(:eof) # returns 1
|
106
|
+
r.read(:eof) # returns 2
|
107
|
+
r.read(:eof) # returns 3
|
108
|
+
r.read(:eof) # returns :eof
|
59
109
|
```
|
60
110
|
|
61
|
-
###
|
111
|
+
### Converting Ruby data to EDN
|
62
112
|
|
63
|
-
|
113
|
+
To convert a data structure to an EDN string:
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
data.to_edn
|
117
|
+
```
|
118
|
+
|
119
|
+
By default, this will work for strings, symbols, numbers, arrays, hashes, sets, nil, Time, and boolean values.
|
120
|
+
|
121
|
+
### Value Translations
|
64
122
|
|
65
|
-
|
123
|
+
Note that EDN uses its own terminology for the types of objects it represents
|
124
|
+
and in some cases those types not map cleanly to Ruby.
|
66
125
|
|
67
|
-
In
|
126
|
+
In EDN, you have _keywords_, which look like Ruby symbols and have the same meaning and
|
127
|
+
purpose. These are converted to Ruby symbols.
|
68
128
|
|
69
|
-
You have
|
129
|
+
You also have EDN _symbols_, which generally reflect variable names, but have
|
130
|
+
several purposes. We parse these and return `EDN::Type::Symbol` values for them,
|
131
|
+
as they don't map to anything built into Ruby. To create an EDN symbol in Ruby,
|
132
|
+
call `EDN::Type::Symbol.new` or `EDN.symbol` with a string argument, or use the
|
133
|
+
convenience unary operator `~` like so: `~"elf/rings"`.
|
70
134
|
|
71
|
-
|
135
|
+
EDN also has _vectors_, which map to Ruby arrays, and _lists_, which are linked lists
|
136
|
+
in Clojure. We map EDN lists to `EDN::Type::List` values, which are type-compatible with
|
137
|
+
arrays. To create an EDN list in Ruby, call `EDN::Type::List.new` or `EDN.list`
|
138
|
+
with all arguments to go in the list. If you have an array, you will use the splat
|
139
|
+
operator, like so: `EDN.list(*[1, 2, 3])`. You can also use the `~` unary
|
140
|
+
operator like so: `~[1, 2, 3]`.
|
72
141
|
|
73
|
-
|
142
|
+
EDN also has character types, but Ruby does not. These are converted into one-character strings.
|
74
143
|
|
75
144
|
### Tagged Values
|
76
145
|
|
77
|
-
The interesting part of
|
146
|
+
The interesting part of EDN is the _extensible_ part.
|
147
|
+
Data can be be _tagged_ to coerce interpretation of
|
148
|
+
it to a particular data type. An example of a tagged data element:
|
78
149
|
|
79
150
|
```
|
80
151
|
#wolf/pack {:alpha "Greybeard" :betas ["Frostpaw" "Blackwind" "Bloodjaw"]}
|
81
152
|
```
|
82
153
|
|
83
|
-
The tag (`#wolf/pack`) will tell any consumers of this data
|
154
|
+
The tag (`#wolf/pack`) will tell any consumers of this data
|
155
|
+
to use a data type registered to handle `wolf/pack` to represent this data.
|
84
156
|
|
85
|
-
The rules for tags from the [
|
157
|
+
The rules for tags from the [EDN README][README] should be followed. In short, custom tags should have a prefix (the part before the `/`) designating the user that created them or context they are used in. Non-prefixed tags are reserved for built-in tags.
|
86
158
|
|
87
159
|
There are two tags built in by default: `#uuid`, used for UUIDs, and `#inst`, used for an instant in time. In `edn-ruby`, `#inst` is converted to a Time, and Time values are tagged as `#inst`. There is not a UUID data type built into Ruby, so `#uuid` is converted to an instance of `EDN::Type::UUID`.
|
88
160
|
|
@@ -156,7 +228,7 @@ end
|
|
156
228
|
|
157
229
|
## Metadata
|
158
230
|
|
159
|
-
Certain elements of
|
231
|
+
Certain elements of EDN can have *metadata*. Metadata is a map of values about the element, which must follow specific rules.
|
160
232
|
|
161
233
|
* Only symbols, lists, vectors, maps, and sets can have metadata. Tagged elements *cannot* have metadata.
|
162
234
|
* Metadata keys must be symbols, keywords, or strings.
|
@@ -165,7 +237,7 @@ Metadata can be expressed in one of the following three ways:
|
|
165
237
|
|
166
238
|
* Via a map. The element is prefixed with a map which has a caret (`^`) prefixed to it, like so: `^{:doc "This is my vector" :rel :temps} [98.6 99.7]`.
|
167
239
|
* Via a keyword. The element is prefixed with a keyword, also prefixed by a caret: `^:awesome #{1 2 \c}`. This results in the key `:awesome` being set to `true`, as if the metadata was: `^{:awesome true} #{1 2 \c}`.
|
168
|
-
* Via a symbol. The element is prefixed with a symbol, also prefixed by a caret: `^Boolean "true"`. This results in the key `:tag` being set to the symbol, as if the metadata was: `^{:tag Boolean} "true"`. This is used in Clojure to indicate the Java type of the element. In other
|
240
|
+
* Via a symbol. The element is prefixed with a symbol, also prefixed by a caret: `^Boolean "true"`. This results in the key `:tag` being set to the symbol, as if the metadata was: `^{:tag Boolean} "true"`. This is used in Clojure to indicate the Java type of the element. In other EDN implementations, it may be ignored or used differently.
|
169
241
|
|
170
242
|
More than one piece of metadata can be applied to an element. Metadata is applied to the next element appearing after it, so in the case of `^:foo ^{:bar false} [1 2]`, the metadata would be, in total, `^{:foo true, :bar false}`. Note that `^:foo` is applied to the element `[1 2]` with the metadata `^{:bar false}` applied to it. Because of this, key collisions are resolved *right-to-left*.
|
171
243
|
|
@@ -175,6 +247,7 @@ More than one piece of metadata can be applied to an element. Metadata is applie
|
|
175
247
|
* Michael Ficarra (@michaelficarra)
|
176
248
|
* Andrew Forward (@aforward)
|
177
249
|
* Gabriel Horner (@cldwalker)
|
250
|
+
* Russ Olsen (@russolsen)
|
178
251
|
|
179
252
|
## Contributing
|
180
253
|
|
data/Rakefile
CHANGED
data/edn.gemspec
CHANGED
@@ -2,11 +2,12 @@
|
|
2
2
|
require File.expand_path('../lib/edn/version', __FILE__)
|
3
3
|
|
4
4
|
Gem::Specification.new do |gem|
|
5
|
-
gem.authors = ["Clinton N. Dreisbach"]
|
6
|
-
gem.email = ["
|
5
|
+
gem.authors = ["Clinton N. Dreisbach & Russ Olsen"]
|
6
|
+
gem.email = ["russ@russolsen.com"]
|
7
7
|
gem.description = %q{'edn implements a reader for Extensible Data Notation by Rich Hickey.'}
|
8
8
|
gem.summary = gem.description
|
9
|
-
gem.homepage = ""
|
9
|
+
gem.homepage = "https://github.com/relevance/edn-ruby"
|
10
|
+
gem.license = "MIT"
|
10
11
|
|
11
12
|
gem.files = `git ls-files`.split($\)
|
12
13
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
@@ -15,7 +16,6 @@ Gem::Specification.new do |gem|
|
|
15
16
|
gem.require_paths = ["lib"]
|
16
17
|
gem.version = EDN::VERSION
|
17
18
|
|
18
|
-
gem.add_dependency 'parslet', '~> 1.4.0'
|
19
19
|
gem.add_development_dependency 'pry', '~> 0.9.10'
|
20
20
|
gem.add_development_dependency 'rspec', '~> 2.11.0'
|
21
21
|
gem.add_development_dependency 'rantly', '~> 0.3.1'
|
data/lib/edn.rb
CHANGED
@@ -2,32 +2,16 @@ $:.push(File.dirname(__FILE__))
|
|
2
2
|
require 'edn/version'
|
3
3
|
require 'edn/core_ext'
|
4
4
|
require 'edn/types'
|
5
|
+
require 'edn/metadata'
|
6
|
+
require 'edn/char_stream'
|
5
7
|
require 'edn/parser'
|
6
|
-
require 'edn/transform'
|
7
8
|
require 'edn/reader'
|
8
9
|
|
9
10
|
module EDN
|
10
|
-
class ParseFailed < StandardError
|
11
|
-
attr_reader :original_exception
|
12
|
-
|
13
|
-
def initialize(message, original_exception)
|
14
|
-
super(message)
|
15
|
-
@original_exception = original_exception
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
@parser = EDN::Parser.new
|
20
|
-
@transform = EDN::Transform.new
|
21
11
|
@tags = Hash.new
|
22
12
|
|
23
|
-
def self.read(edn)
|
24
|
-
|
25
|
-
tree = @parser.parse(edn)
|
26
|
-
rescue Parslet::ParseFailed => error
|
27
|
-
message = "Invalid EDN, cannot parse: #{edn}"
|
28
|
-
raise ParseFailed.new(message, error)
|
29
|
-
end
|
30
|
-
@transform.apply(tree)
|
13
|
+
def self.read(edn, eof_value=NOTHING)
|
14
|
+
EDN::Reader.new(edn).read(eof_value)
|
31
15
|
end
|
32
16
|
|
33
17
|
def self.register(tag, func = nil, &block)
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module EDN
|
5
|
+
class CharStream
|
6
|
+
def initialize(io=$stdin)
|
7
|
+
@io = io
|
8
|
+
@current = nil
|
9
|
+
end
|
10
|
+
|
11
|
+
def current
|
12
|
+
return @current if @current
|
13
|
+
advance
|
14
|
+
end
|
15
|
+
|
16
|
+
def advance
|
17
|
+
return @current if @current == :eof
|
18
|
+
@current = @io.getc || :eof
|
19
|
+
end
|
20
|
+
|
21
|
+
def digit?(c=current)
|
22
|
+
/[0-9]/ =~ c
|
23
|
+
end
|
24
|
+
|
25
|
+
def alpha?(c=current)
|
26
|
+
/[a-zA-Z]/ =~ c
|
27
|
+
end
|
28
|
+
|
29
|
+
def eof?(c=current)
|
30
|
+
c == :eof
|
31
|
+
end
|
32
|
+
|
33
|
+
def ws?(c=current)
|
34
|
+
/[ \t\n,]/ =~ c
|
35
|
+
end
|
36
|
+
|
37
|
+
def newline?(c=current)
|
38
|
+
/[\n\r]/ =~ c
|
39
|
+
end
|
40
|
+
|
41
|
+
def repeat(pattern, &block)
|
42
|
+
result = nil
|
43
|
+
while current =~ pattern
|
44
|
+
result ||= ''
|
45
|
+
result = block.call(result, current)
|
46
|
+
end
|
47
|
+
result
|
48
|
+
end
|
49
|
+
|
50
|
+
def gather(pattern)
|
51
|
+
repeat(pattern) do |result, ch|
|
52
|
+
result << ch
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def skip_past(expected, error_message=nil)
|
57
|
+
if current == expected
|
58
|
+
advance
|
59
|
+
return expected
|
60
|
+
elsif error_message
|
61
|
+
raise error_message
|
62
|
+
end
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
|
66
|
+
def skip_to_eol
|
67
|
+
until current == :eof || newline?
|
68
|
+
advance
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def skip_ws
|
73
|
+
while current != :eof
|
74
|
+
if ws?(current)
|
75
|
+
advance
|
76
|
+
elsif current == ';'
|
77
|
+
skip_to_eol
|
78
|
+
else
|
79
|
+
break
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
data/lib/edn/parser.rb
CHANGED
@@ -1,205 +1,337 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
1
|
+
require 'stringio'
|
2
|
+
require 'set'
|
3
|
+
require 'pry'
|
4
|
+
|
3
5
|
|
4
6
|
module EDN
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
(
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
7
|
+
|
8
|
+
# Object returned when there is nothing to return
|
9
|
+
|
10
|
+
NOTHING = Object.new
|
11
|
+
|
12
|
+
# Object to return when we hit end of file. Cant be nil or :eof
|
13
|
+
# because either of those could be something in the EDN data.
|
14
|
+
|
15
|
+
EOF = Object.new
|
16
|
+
|
17
|
+
# Reader table
|
18
|
+
|
19
|
+
READERS = {}
|
20
|
+
SYMBOL_INTERIOR_CHARS =
|
21
|
+
Set.new(%w{. # * ! - _ + ? $ % & = < > :} + ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a)
|
22
|
+
|
23
|
+
SYMBOL_INTERIOR_CHARS.each {|n| READERS[n.to_s] = :read_symbol}
|
24
|
+
|
25
|
+
DIGITS = Set.new(('0'..'9').to_a)
|
26
|
+
|
27
|
+
DIGITS.each {|n| READERS[n.to_s] = :read_number}
|
28
|
+
|
29
|
+
READERS.default = :unknown
|
30
|
+
|
31
|
+
READERS['{'] = :read_map
|
32
|
+
READERS['['] = :read_vector
|
33
|
+
READERS['('] = :read_list
|
34
|
+
READERS['\\'] = :read_char
|
35
|
+
READERS['"'] = :read_string
|
36
|
+
READERS['.'] = :read_number_or_symbol
|
37
|
+
READERS['+'] = :read_number_or_symbol
|
38
|
+
READERS['-'] = :read_number_or_symbol
|
39
|
+
READERS[''] = :read_number_or_symbol
|
40
|
+
READERS['/'] = :read_slash
|
41
|
+
READERS[':'] = :read_keyword
|
42
|
+
READERS['#'] = :read_extension
|
43
|
+
READERS[:eof] = :read_eof
|
44
|
+
|
45
|
+
def self.register_reader(ch, handler=nil, &block)
|
46
|
+
if handler
|
47
|
+
READERS[ch] = handler
|
48
|
+
else
|
49
|
+
READERS[ch] = block
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
TAGS = {}
|
54
|
+
|
55
|
+
def self.register(tag, func = nil, &block)
|
56
|
+
if block_given?
|
57
|
+
func = block
|
58
|
+
end
|
59
|
+
|
60
|
+
if func.nil?
|
61
|
+
func = lambda { |x| x }
|
62
|
+
end
|
63
|
+
|
64
|
+
if func.is_a?(Class)
|
65
|
+
TAGS[tag] = lambda { |*args| func.new(*args) }
|
66
|
+
else
|
67
|
+
TAGS[tag] = func
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.unregister(tag)
|
72
|
+
TAGS[tag] = nil
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.tagged_element(tag, element)
|
76
|
+
func = TAGS[tag]
|
77
|
+
if func
|
78
|
+
func.call(element)
|
79
|
+
else
|
80
|
+
EDN::Type::Unknown.new(tag, element)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class Parser
|
85
|
+
def initialize(source, *extra)
|
86
|
+
io = source.instance_of?(String) ? StringIO.new(source) : source
|
87
|
+
@s = CharStream.new(io)
|
88
|
+
end
|
89
|
+
|
90
|
+
def read
|
91
|
+
meta = read_meta
|
92
|
+
value = read_basic
|
93
|
+
if meta
|
94
|
+
value.extend EDN::Metadata
|
95
|
+
value.metadata = meta
|
96
|
+
end
|
97
|
+
value
|
98
|
+
end
|
99
|
+
|
100
|
+
def eof?
|
101
|
+
@s.eof?
|
102
|
+
end
|
103
|
+
|
104
|
+
def unknown
|
105
|
+
raise "Don't know what to do with #{@s.current} #{@s.current.class}"
|
106
|
+
end
|
107
|
+
|
108
|
+
def read_eof
|
109
|
+
EOF
|
110
|
+
end
|
111
|
+
|
112
|
+
def read_char
|
113
|
+
result = @s.advance
|
114
|
+
@s.advance
|
115
|
+
until @s.eof?
|
116
|
+
break unless @s.digit? || @s.alpha?
|
117
|
+
result += @s.current
|
118
|
+
@s.advance
|
119
|
+
end
|
120
|
+
|
121
|
+
return result if result.size == 1
|
122
|
+
|
123
|
+
case result
|
124
|
+
when 'newline'
|
125
|
+
"\n"
|
126
|
+
when 'return'
|
127
|
+
"\r"
|
128
|
+
when 'tab'
|
129
|
+
"\t"
|
130
|
+
when 'space'
|
131
|
+
" "
|
132
|
+
else
|
133
|
+
raise "Unknown char #{result}"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def read_slash
|
138
|
+
@s.advance
|
139
|
+
Type::Symbol.new('/')
|
140
|
+
end
|
141
|
+
|
142
|
+
def read_number_or_symbol
|
143
|
+
leading = @s.current
|
144
|
+
@s.advance
|
145
|
+
return read_number(leading) if @s.digit?
|
146
|
+
read_symbol(leading)
|
147
|
+
end
|
148
|
+
|
149
|
+
def read_symbol_chars
|
150
|
+
result = ''
|
151
|
+
|
152
|
+
ch = @s.current
|
153
|
+
while SYMBOL_INTERIOR_CHARS.include?(ch)
|
154
|
+
result << ch
|
155
|
+
ch = @s.advance
|
156
|
+
end
|
157
|
+
return result unless @s.skip_past('/')
|
158
|
+
|
159
|
+
result << '/'
|
160
|
+
ch = @s.current
|
161
|
+
while SYMBOL_INTERIOR_CHARS.include?(ch)
|
162
|
+
result << ch
|
163
|
+
ch = @s.advance
|
164
|
+
end
|
165
|
+
|
166
|
+
result
|
167
|
+
end
|
168
|
+
|
169
|
+
def read_extension
|
170
|
+
@s.advance
|
171
|
+
if @s.current == '{'
|
172
|
+
@s.advance
|
173
|
+
read_collection(Set, '}')
|
174
|
+
elsif @s.current == "_"
|
175
|
+
@s.advance
|
176
|
+
x = read
|
177
|
+
NOTHING
|
178
|
+
else
|
179
|
+
tag = read_symbol_chars
|
180
|
+
value = read
|
181
|
+
EDN.tagged_element(tag, value)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def read_symbol(leading='')
|
186
|
+
token = leading + read_symbol_chars
|
187
|
+
return true if token == "true"
|
188
|
+
return false if token == "false"
|
189
|
+
return nil if token == "nil"
|
190
|
+
Type::Symbol.new(token)
|
191
|
+
end
|
192
|
+
|
193
|
+
def read_keyword
|
194
|
+
@s.advance
|
195
|
+
read_symbol_chars.to_sym
|
196
|
+
end
|
197
|
+
|
198
|
+
def escape_char(ch)
|
199
|
+
return '\\' if ch == '\\'
|
200
|
+
return "\n" if ch == 'n'
|
201
|
+
return "\t" if ch == 't'
|
202
|
+
return "\r" if ch == 'r'
|
203
|
+
ch
|
204
|
+
end
|
205
|
+
|
206
|
+
def read_string
|
207
|
+
@s.advance
|
208
|
+
|
209
|
+
result = ''
|
210
|
+
until @s.current == '"'
|
211
|
+
raise "Unexpected eof" if @s.eof?
|
212
|
+
if @s.current == '\\'
|
213
|
+
@s.advance
|
214
|
+
result << escape_char(@s.current)
|
215
|
+
else
|
216
|
+
result << @s.current
|
217
|
+
end
|
218
|
+
@s.advance
|
219
|
+
end
|
220
|
+
@s.advance
|
221
|
+
result
|
222
|
+
end
|
223
|
+
|
224
|
+
def call_reader(reader)
|
225
|
+
if reader.instance_of? Symbol
|
226
|
+
self.send(reader)
|
227
|
+
else
|
228
|
+
self.instance_exec(&reader)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def read_basic
|
233
|
+
@s.skip_ws
|
234
|
+
ch = @s.current
|
235
|
+
result = call_reader(READERS[ch])
|
236
|
+
while result == NOTHING
|
237
|
+
@s.skip_ws
|
238
|
+
result = call_reader(READERS[@s.current])
|
239
|
+
end
|
240
|
+
result
|
241
|
+
end
|
242
|
+
|
243
|
+
def read_digits(min_digits=0)
|
244
|
+
result = ''
|
245
|
+
|
246
|
+
if @s.current == '+' || @s.current == '-'
|
247
|
+
result << @s.current
|
248
|
+
@s.advance
|
249
|
+
end
|
250
|
+
|
251
|
+
n_digits = 0
|
252
|
+
while @s.current =~ /[0-9]/
|
253
|
+
n_digits += 1
|
254
|
+
result << @s.current
|
255
|
+
@s.advance
|
256
|
+
end
|
257
|
+
|
258
|
+
raise "Expected at least #{min_digits} digits, found #{result}" unless n_digits >= min_digits
|
259
|
+
result
|
260
|
+
end
|
261
|
+
|
262
|
+
def finish_float(whole_part)
|
263
|
+
result = whole_part
|
264
|
+
result += @s.skip_past('.', 'Expected .')
|
265
|
+
result += read_digits(1)
|
266
|
+
if @s.current == 'e' || @s.current == 'E'
|
267
|
+
@s.advance
|
268
|
+
result = result + 'e' + read_digits
|
269
|
+
end
|
270
|
+
result.to_f
|
271
|
+
end
|
272
|
+
|
273
|
+
def read_number(leading='')
|
274
|
+
result = leading + read_digits
|
275
|
+
|
276
|
+
if @s.current == '.'
|
277
|
+
return finish_float(result)
|
278
|
+
elsif @s.skip_past('M') || @s.skip_past('N')
|
279
|
+
result.to_i
|
280
|
+
else
|
281
|
+
result.to_i
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
def read_meta
|
286
|
+
raw_metadata = []
|
287
|
+
@s.skip_ws
|
288
|
+
while @s.current == '^'
|
289
|
+
@s.advance
|
290
|
+
raw_metadata << read_basic
|
291
|
+
@s.skip_ws
|
292
|
+
end
|
293
|
+
|
294
|
+
metadata = raw_metadata.reverse.reduce({}) do |acc, m|
|
295
|
+
case m
|
296
|
+
when Symbol then acc.merge(m => true)
|
297
|
+
when EDN::Type::Symbol then acc.merge(:tag => m)
|
298
|
+
else acc.merge(m)
|
299
|
+
end
|
300
|
+
end
|
301
|
+
metadata.empty? ? nil : metadata
|
302
|
+
end
|
303
|
+
|
304
|
+
def read_list
|
305
|
+
@s.advance
|
306
|
+
read_collection(EDN::Type::List, ')')
|
307
|
+
end
|
308
|
+
|
309
|
+
def read_vector
|
310
|
+
@s.advance
|
311
|
+
read_collection(Array, ']')
|
312
|
+
end
|
313
|
+
|
314
|
+
def read_map
|
315
|
+
@s.advance
|
316
|
+
array = read_collection(Array, '}')
|
317
|
+
raise "Need an even number of items for a map" unless array.count.even?
|
318
|
+
Hash[*array]
|
319
|
+
end
|
320
|
+
|
321
|
+
def read_collection(clazz, closing)
|
322
|
+
result = clazz.new
|
323
|
+
|
324
|
+
@s.skip_ws
|
325
|
+
|
326
|
+
ch = @s.current
|
327
|
+
while ch != closing
|
328
|
+
raise "Unexpected eof" if ch == :eof
|
329
|
+
result << read
|
330
|
+
@s.skip_ws
|
331
|
+
ch = @s.current
|
332
|
+
end
|
333
|
+
@s.advance
|
334
|
+
result
|
335
|
+
end
|
204
336
|
end
|
205
337
|
end
|