clj 0.0.1

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 (4) hide show
  1. data/lib/clj.rb +24 -0
  2. data/lib/clj/parser.rb +246 -0
  3. data/lib/clj/types.rb +45 -0
  4. metadata +70 -0
data/lib/clj.rb ADDED
@@ -0,0 +1,24 @@
1
+ #--
2
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
+ # Version 2, December 2004
4
+ #
5
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
+ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ #
8
+ # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ #++
10
+
11
+ require 'clj/types'
12
+ require 'clj/parser'
13
+
14
+ class Clojure
15
+ def self.parse (*args)
16
+ Clojure::Parser.new(*args).parse
17
+ end
18
+
19
+ def self.dump (what)
20
+ raise ArgumentError, 'cannot convert the passed value to clojure' unless what.respond_to? :to_clj
21
+
22
+ what.to_clj
23
+ end
24
+ end
data/lib/clj/parser.rb ADDED
@@ -0,0 +1,246 @@
1
+ #--
2
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
+ # Version 2, December 2004
4
+ #
5
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
+ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ #
8
+ # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ #++
10
+
11
+ require 'strscan'
12
+
13
+ class Clojure
14
+
15
+ class Parser < StringScanner
16
+ STRING = /" ((?:[^\x0-\x1f"\\] |
17
+ # escaped special characters:
18
+ \\["\\\/bfnrt] |
19
+ \\u[0-9a-fA-F]{4} |
20
+ # match all but escaped special characters:
21
+ \\[\x20-\x21\x23-\x2e\x30-\x5b\x5d-\x61\x63-\x65\x67-\x6d\x6f-\x71\x73\x75-\xff])*)
22
+ "/nx
23
+
24
+ KEYWORD = /:([^(\[{'^@`~\"\\,\s;)\]}])/
25
+
26
+ INTEGER = /(-?0|-?[1-9]\d*)/
27
+
28
+ FLOAT = /(-?
29
+ (?:0|[1-9]\d*)
30
+ (?:
31
+ \.\d+(?i:e[+-]?\d+) |
32
+ \.\d+ |
33
+ (?i:e[+-]?\d+)
34
+ )
35
+ )/x
36
+
37
+ RATIONAL = /(#{INTEGER}\/#{INTEGER})/
38
+
39
+ REGEXP = /#"((\\.|[^"])+)"/
40
+
41
+ VECTOR_OPEN = /\[/
42
+ VECTOR_CLOSE = /\]/
43
+
44
+ LIST_OPEN = /\(/
45
+ LIST_CLOSE = /\)/
46
+
47
+ SET_OPEN = /\#\{/
48
+ SET_CLOSE = /\}/
49
+
50
+ HASH_OPEN = /\{/
51
+ HASH_CLOSE = /\}/
52
+
53
+ TRUE = /true/
54
+ FALSE = /false/
55
+ NIL = /nil/
56
+
57
+ IGNORE = %r(
58
+ (?:
59
+ //[^\n\r]*[\n\r]| # line comments
60
+ /\* # c-style comments
61
+ (?:
62
+ [^*/]| # normal chars
63
+ /[^*]| # slashes that do not start a nested comment
64
+ \*[^/]| # asterisks that do not end this comment
65
+ /(?=\*/) # single slash before this comment's end
66
+ )*
67
+ \*/ # the End of this comment
68
+ |[ \t\r\n,]+ # whitespaces: space, horicontal tab, lf, cr, and comma
69
+ )+
70
+ )mx
71
+
72
+ UNPARSED = Object.new
73
+
74
+ def initialize (source, options = {})
75
+ super(source)
76
+
77
+ @hash_class = options[:hash_class] || Hash
78
+ @vector_class = options[:vector_class] || Array
79
+ @list_class = options[:list_class] || Array
80
+ @set_class = options[:set_class] || Array
81
+ end
82
+
83
+ alias source string
84
+
85
+ def parsable? (what)
86
+ !!case what
87
+ when :vector then scan(VECTOR_OPEN)
88
+ when :list then scan(LIST_OPEN)
89
+ when :set then scan(SET_OPEN)
90
+ when :hash then scan(HASH_OPEN)
91
+ end
92
+ end
93
+
94
+ def parse (check = true)
95
+ reset if check
96
+
97
+ result = case
98
+ when parsable?(:vector) then parse_vector
99
+ when parsable?(:list) then parse_list
100
+ when parsable?(:set) then parse_set
101
+ when parsable?(:hash) then parse_hash
102
+ else parse_value
103
+ end
104
+
105
+ if check && result == UNPARSED
106
+ raise SyntaxError, 'the string does not contain proper clojure'
107
+ end
108
+
109
+ result
110
+ end
111
+
112
+ def parse_value
113
+ case
114
+ when scan(RATIONAL) then Rational(self[1])
115
+ when scan(FLOAT) then Float(self[1])
116
+ when scan(INTEGER) then Integer(self[1])
117
+ when scan(REGEXP) then /#{self[1]}/
118
+ when scan(STRING) then parse_string
119
+ when scan(KEYWORD) then self[1].to_sym
120
+ when scan(TRUE) then true
121
+ when scan(FALSE) then false
122
+ when scan(NIL) then nil
123
+ else UNPARSED
124
+ end
125
+ end
126
+
127
+ # Unescape characters in strings.
128
+ UNESCAPE_MAP = Hash.new { |h, k| h[k] = k.chr }
129
+ UNESCAPE_MAP.update({
130
+ ?" => '"',
131
+ ?\\ => '\\',
132
+ ?/ => '/',
133
+ ?b => "\b",
134
+ ?f => "\f",
135
+ ?n => "\n",
136
+ ?r => "\r",
137
+ ?t => "\t",
138
+ ?u => nil,
139
+ })
140
+
141
+ def parse_string
142
+ return '' if self[1].empty?
143
+
144
+ self[1].gsub(%r((?:\\[\\bfnrt"/]|(?:\\u(?:[A-Fa-f\d]{4}))+|\\[\x20-\xff]))n) do |c|
145
+ if u = UNESCAPE_MAP[$&[1]]
146
+ u
147
+ else # \uXXXX
148
+ bytes = EMPTY_8BIT_STRING.dup
149
+ i = 0
150
+ while c[6 * i] == ?\\ && c[6 * i + 1] == ?u
151
+ bytes << c[6 * i + 2, 2].to_i(16) << c[6 * i + 4, 2].to_i(16)
152
+ i += 1
153
+ end
154
+ JSON.iconv('utf-8', 'utf-16be', bytes)
155
+ end
156
+ end
157
+ end
158
+
159
+ def parse_vector
160
+ result = @vector_class.new
161
+
162
+ until eos?
163
+ case
164
+ when (value = parse(false)) != UNPARSED
165
+ result << value
166
+ when scan(VECTOR_CLOSE)
167
+ break
168
+ when skip(IGNORE)
169
+ ;
170
+ else
171
+ raise SyntaxError, 'wat do'
172
+ end
173
+ end
174
+
175
+ result
176
+ end
177
+
178
+ def parse_list
179
+ result = @list_class.new
180
+
181
+ until eos?
182
+ case
183
+ when (value = parse(false)) != UNPARSED
184
+ result << value
185
+ when scan(LIST_CLOSE)
186
+ break
187
+ when skip(IGNORE)
188
+ ;
189
+ else
190
+ raise SyntaxError, 'wat do'
191
+ end
192
+ end
193
+
194
+ result
195
+ end
196
+
197
+ def parse_set
198
+ result = @set_class.new
199
+
200
+ until eos?
201
+ case
202
+ when (value = parse(false)) != UNPARSED
203
+ result << value
204
+ when scan(SET_CLOSE)
205
+ break
206
+ when skip(IGNORE)
207
+ ;
208
+ else
209
+ raise SyntaxError, 'wat do'
210
+ end
211
+ end
212
+
213
+ if result.uniq!
214
+ raise SyntaxError, 'the set contains non unique values'
215
+ end
216
+
217
+ result
218
+ end
219
+
220
+ def parse_hash
221
+ result = @hash_class.new
222
+
223
+ until eos?
224
+ case
225
+ when (key = parse(false)) != UNPARSED
226
+ skip(IGNORE)
227
+
228
+ if (value = parse(false)) == UNPARSED
229
+ raise SyntaxError, 'no value for the hash'
230
+ end
231
+
232
+ result[key] = value
233
+ when scan(HASH_CLOSE)
234
+ break
235
+ when skip(IGNORE)
236
+ ;
237
+ else
238
+ raise SyntaxError, 'wat do'
239
+ end
240
+ end
241
+
242
+ result
243
+ end
244
+ end
245
+
246
+ end
data/lib/clj/types.rb ADDED
@@ -0,0 +1,45 @@
1
+ #--
2
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
+ # Version 2, December 2004
4
+ #
5
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
+ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ #
8
+ # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ #++
10
+
11
+ [String, Symbol, Numeric, TrueClass, FalseClass, NilClass].each {|klass|
12
+ klass.instance_eval {
13
+ alias_method :to_clj, :inspect
14
+ }
15
+ }
16
+
17
+ class Rational
18
+ alias to_clj to_s
19
+ end
20
+
21
+ class Regexp
22
+ def to_clj
23
+ '#"' + inspect[1 .. -2] + '"'
24
+ end
25
+ end
26
+
27
+ if defined? BigDecimal
28
+ class BigDecimal
29
+ def to_clj
30
+ inspect + 'M'
31
+ end
32
+ end
33
+ end
34
+
35
+ class Array
36
+ def to_clj
37
+ '[' + map { |o| o.to_clj }.join(' ') + ']'
38
+ end
39
+ end
40
+
41
+ class Hash
42
+ def to_clj
43
+ '{' + map { |k, v| k.to_clj + ' ' + v.to_clj }.join(' ') + '}'
44
+ end
45
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: clj
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - meh.
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-02-02 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ version_requirements: &14424 !ruby/object:Gem::Requirement
16
+ none: false
17
+ requirements:
18
+ - - ! '>='
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ prerelease: false
22
+ requirement: *14424
23
+ name: rake
24
+ type: :development
25
+ - !ruby/object:Gem::Dependency
26
+ version_requirements: &14488 !ruby/object:Gem::Requirement
27
+ none: false
28
+ requirements:
29
+ - - ! '>='
30
+ - !ruby/object:Gem::Version
31
+ version: '0'
32
+ prerelease: false
33
+ requirement: *14488
34
+ name: rspec
35
+ type: :development
36
+ description:
37
+ email: meh@paranoici.org
38
+ executables: []
39
+ extensions: []
40
+ extra_rdoc_files: []
41
+ files:
42
+ - lib/clj.rb
43
+ - lib/clj/types.rb
44
+ - lib/clj/parser.rb
45
+ homepage: http://github.com/meh/ruby-clj
46
+ licenses: []
47
+ post_install_message:
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ! '>='
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ requirements: []
64
+ rubyforge_project:
65
+ rubygems_version: 1.8.12
66
+ signing_key:
67
+ specification_version: 3
68
+ summary: Like json, but with clojure sexps.
69
+ test_files: []
70
+ has_rdoc: