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.
- data/lib/clj.rb +24 -0
- data/lib/clj/parser.rb +246 -0
- data/lib/clj/types.rb +45 -0
- 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:
|