cddl 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/cddl +42 -0
- data/cddl.gemspec +19 -0
- data/data/cddl.abnf +62 -0
- data/data/prelude.cddl +43 -0
- data/lib/cddl.rb +442 -0
- data/test-data/7071-concise.cddl +17 -0
- data/test-data/7071-verbose.cddl +42 -0
- data/test/test-cddl.rb +292 -0
- metadata +95 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: bc6439f876088c1d9355f78363bfa34eaa844613
|
4
|
+
data.tar.gz: eb88cc041f3a03ef495aeecf4b663af0a2384673
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 449e97a60fb3ee5584b582dc8da13d54de607f99ce02020a5565d7495142a8547965429b9d429582a2c898f8c6680a64d089339ff6c8fe4def07887da9ddd782
|
7
|
+
data.tar.gz: af6dd64fe9c1267fe4f86fbf4825e7da9faa4be03545991369b647c5766e5735fb1c760341bdf0afb7a4f07f3f7f06279bb1e900c459795eaf150c5c76cbb9a1
|
data/bin/cddl
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
require 'cddl'
|
4
|
+
require 'cbor-diagnostic'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
Encoding.default_external = "UTF-8" # wake up, smell the coffee
|
8
|
+
|
9
|
+
def usage
|
10
|
+
warn "Usage:"
|
11
|
+
warn "#$0 spec.cddl generate [n]"
|
12
|
+
warn "#$0 spec.cddl json-generate [n]"
|
13
|
+
warn "#$0 spec.cddl validate instance.cbor"
|
14
|
+
warn "#$0 spec.cddl validate instance.json"
|
15
|
+
exit 1
|
16
|
+
end
|
17
|
+
|
18
|
+
case ARGV[1]
|
19
|
+
when /\Ag/
|
20
|
+
parser = CDDL::Parser.new(File.read(ARGV[0]))
|
21
|
+
n = 1
|
22
|
+
n = ARGV[2].to_i if ARGV[2]
|
23
|
+
n.times do
|
24
|
+
g = parser.generate
|
25
|
+
puts g.cbor_diagnostic
|
26
|
+
end
|
27
|
+
when /\Aj/
|
28
|
+
parser = CDDL::Parser.new(File.read(ARGV[0]))
|
29
|
+
n = 1
|
30
|
+
n = ARGV[2].to_i if ARGV[2]
|
31
|
+
n.times do
|
32
|
+
g = parser.generate
|
33
|
+
puts JSON.pretty_generate(g)
|
34
|
+
end
|
35
|
+
when /\Av/
|
36
|
+
parser = CDDL::Parser.new(File.read(ARGV[0]))
|
37
|
+
instance = File.read(ARGV[2])
|
38
|
+
instance = CBOR.decode(instance) rescue JSON.load(instance)
|
39
|
+
p parser.validate(instance)
|
40
|
+
else
|
41
|
+
usage
|
42
|
+
end
|
data/cddl.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
spec = Gem::Specification.new do |s|
|
2
|
+
s.name = 'cddl'
|
3
|
+
s.version = '0.1.0'
|
4
|
+
s.summary = "CDDL generator and validator."
|
5
|
+
s.description = %{A parser, generator, and validator for CDDL}
|
6
|
+
s.add_dependency('cbor-diag')
|
7
|
+
s.add_dependency('abnc')
|
8
|
+
s.add_dependency('json')
|
9
|
+
s.files = `git ls-files`.split("\n").grep(/^[a-z]/)
|
10
|
+
s.files = Dir['lib/**/*.rb'] + %w(cddl.gemspec) + Dir['data/**/*.abnf'] + Dir['data/**/*.cddl'] + Dir['test-data/**/*.cddl'] + Dir['test/**/*.rb']
|
11
|
+
s.require_path = 'lib'
|
12
|
+
s.executables = ['cddl']
|
13
|
+
s.default_executable = 'cddl'
|
14
|
+
s.required_ruby_version = '>= 1.9.2'
|
15
|
+
s.author = "Carsten Bormann"
|
16
|
+
s.email = "cabo@tzi.org"
|
17
|
+
s.homepage = "http://github.com/cabo/cddl"
|
18
|
+
s.license = 'MIT'
|
19
|
+
end
|
data/data/cddl.abnf
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
cddl = S 1*rule
|
2
|
+
rule = typename S "=" S type S
|
3
|
+
/ groupname S "=" S group S
|
4
|
+
|
5
|
+
typename = id
|
6
|
+
groupname = id
|
7
|
+
|
8
|
+
type = type1 S *("/" S type1 S)
|
9
|
+
|
10
|
+
type1 = value
|
11
|
+
/ "#" "6" ["." uint] "(" S type S ")" ; note no space!
|
12
|
+
/ "#" DIGIT ["." uint] ; major/ai
|
13
|
+
/ "#" ; any
|
14
|
+
/ "{" S group S "}"
|
15
|
+
/ "[" S group S "]"
|
16
|
+
/ "(" type ")"
|
17
|
+
/ typename
|
18
|
+
|
19
|
+
group = "(" S *grpent S ")"
|
20
|
+
/ *grpent
|
21
|
+
|
22
|
+
grpent = [occur S] [memberkey S] type1 optcom
|
23
|
+
/ [occur S] groupname optcom ; always preempted by previous...
|
24
|
+
|
25
|
+
memberkey = membername S ":"
|
26
|
+
membername = bareword ; note that this can be a typename
|
27
|
+
/ type1 ; can be preempted by previous...
|
28
|
+
|
29
|
+
bareword = id
|
30
|
+
|
31
|
+
optcom = S ["," S]
|
32
|
+
|
33
|
+
occur = [uint] "*" [uint]
|
34
|
+
/ "+"
|
35
|
+
/ "?"
|
36
|
+
|
37
|
+
uint = "0"
|
38
|
+
/ DIGIT1 *DIGIT
|
39
|
+
|
40
|
+
value = int
|
41
|
+
/ float
|
42
|
+
/ string
|
43
|
+
|
44
|
+
int = ["-"] uint
|
45
|
+
|
46
|
+
float = int ["." 1*DIGIT] ["e" int ]
|
47
|
+
|
48
|
+
string = %x22 *SCHAR %x22
|
49
|
+
SCHAR = %x20-21 / %x23-7E / SESC
|
50
|
+
SESC = "\" %x20-7E
|
51
|
+
|
52
|
+
id = ALPHA *(ALPHA / DIGIT / "_" / "-")
|
53
|
+
ALPHA = %x41-5A / %x61-7A
|
54
|
+
DIGIT = %x30-39
|
55
|
+
DIGIT1 = %x31-39
|
56
|
+
S = *WS
|
57
|
+
WS = SP / NL
|
58
|
+
SP = %x20
|
59
|
+
NL = COMMENT / CRLF
|
60
|
+
COMMENT = ";" *(SP / VCHAR) CRLF
|
61
|
+
VCHAR = %x21-7E
|
62
|
+
CRLF = %x0A / %x0D.0A
|
data/data/prelude.cddl
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
; --- prelude ---
|
2
|
+
|
3
|
+
any = #
|
4
|
+
|
5
|
+
uint = #0
|
6
|
+
nint = #1
|
7
|
+
int = uint / nint
|
8
|
+
|
9
|
+
bstr = #2
|
10
|
+
tstr = #3
|
11
|
+
|
12
|
+
tdate = #6.0(tstr)
|
13
|
+
time = #6.1(number)
|
14
|
+
number = int / float
|
15
|
+
biguint = #6.2(bstr)
|
16
|
+
bignint = #6.3(bstr)
|
17
|
+
bigint = biguint / bignint
|
18
|
+
integer = int / bigint
|
19
|
+
decfrac = #6.4([e10: int, m: integer])
|
20
|
+
bigfloat = #6.5([e2: int, m: integer])
|
21
|
+
eb64url = #6.21(any)
|
22
|
+
eb64legacy = #6.21(any)
|
23
|
+
eb16 = #6.21(any)
|
24
|
+
encoded-cbor = #6.24(bstr)
|
25
|
+
uri = #6.32(tstr)
|
26
|
+
b64url = #6.33(tstr)
|
27
|
+
b64legacy = #6.34(tstr)
|
28
|
+
regexp = #6.35(tstr)
|
29
|
+
mime-message = #6.36(tstr)
|
30
|
+
cbor-any = #6.55799(any)
|
31
|
+
|
32
|
+
float16 = #7.25
|
33
|
+
float32 = #7.26
|
34
|
+
float64 = #7.27
|
35
|
+
float16-32 = float16 / float32
|
36
|
+
float32-64 = float32 / float64
|
37
|
+
float = float16-32 / float64
|
38
|
+
|
39
|
+
false = #7.20
|
40
|
+
true = #7.21
|
41
|
+
nil = #7.22
|
42
|
+
undefined = #7.23
|
43
|
+
|
data/lib/cddl.rb
ADDED
@@ -0,0 +1,442 @@
|
|
1
|
+
require 'abnc'
|
2
|
+
require 'pp'
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
module CDDL
|
6
|
+
|
7
|
+
DATA_DIR = Pathname.new(__FILE__).split[0] + '../data'
|
8
|
+
|
9
|
+
PRELUDE = File.read("#{DATA_DIR}/prelude.cddl")
|
10
|
+
ABNF_SPEC = File.read("#{DATA_DIR}/cddl.abnf")
|
11
|
+
|
12
|
+
class Parser
|
13
|
+
def initialize(source_text)
|
14
|
+
@abnf = Peggy::ABNF.new
|
15
|
+
_cresult = @abnf.compile! ABNF_SPEC, ignore: :s
|
16
|
+
presult = @abnf.parse? :cddl, (source_text + PRELUDE)
|
17
|
+
expected_length = source_text.length + PRELUDE.length
|
18
|
+
if expected_length != presult
|
19
|
+
upto = @abnf.parse_results.keys.max
|
20
|
+
puts "UPTO: #{upto}"
|
21
|
+
pp @abnf.parse_results[upto]
|
22
|
+
pp @abnf.parse_results[presult]
|
23
|
+
p presult
|
24
|
+
puts @abnf.ast?
|
25
|
+
raise "parse error at #{presult} upto #{upto} of #{expected_length}"
|
26
|
+
end
|
27
|
+
puts @abnf.ast? if $debug_ast
|
28
|
+
@ast = @abnf.ast?
|
29
|
+
end
|
30
|
+
|
31
|
+
def apr # for debugging
|
32
|
+
@abnf.parse_results
|
33
|
+
end
|
34
|
+
|
35
|
+
def rules
|
36
|
+
@rules = {}
|
37
|
+
ast.each :rule do |rule|
|
38
|
+
# p rule
|
39
|
+
if rulename = rule.groupname
|
40
|
+
fail "Duplicate rule #{rulename.to_s}" if @rules[rulename.to_s]
|
41
|
+
@rules[rulename.to_s] = [:grpent, *rule.group.children(:grpent)]
|
42
|
+
elsif rulename = rule.typename
|
43
|
+
fail "Duplicate rule #{rulename.to_s}" if @rules[rulename.to_s]
|
44
|
+
@rules[rulename.to_s] = [:type1, *rule.type.children(:type1)]
|
45
|
+
else
|
46
|
+
fail "Huh?"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
# pp @rules
|
50
|
+
@rootrule = @rules.keys.first
|
51
|
+
# now process the rules...
|
52
|
+
@stage1 = {}
|
53
|
+
r_process(@rootrule, @rules[@rootrule])
|
54
|
+
# @rules.each do |n, r| # debug only loop
|
55
|
+
# r_process(n, r)
|
56
|
+
# end
|
57
|
+
# @stage1
|
58
|
+
end
|
59
|
+
|
60
|
+
def generate(where=rules, inmap = false)
|
61
|
+
case where[0]
|
62
|
+
when :type1
|
63
|
+
generate(where[rand(where.size-1)+1])
|
64
|
+
when :map
|
65
|
+
Hash[where[1..-1].flat_map {|m| generate(m, true)}]
|
66
|
+
when :array
|
67
|
+
where[1..-1].flat_map {|m| generate(m).map{|e| e[1]}}
|
68
|
+
when :member
|
69
|
+
s = where[1]
|
70
|
+
e = where[2]
|
71
|
+
e = 4 if e == -1
|
72
|
+
s += rand(e + 1 - s) if e != s
|
73
|
+
kr = where[3]
|
74
|
+
vr = where[4]
|
75
|
+
fail "member key not given in map for #{where}" unless kr if inmap
|
76
|
+
Array.new(s) { [ (generate(kr) if kr), # XXX: need error in map context
|
77
|
+
generate(vr)
|
78
|
+
]}
|
79
|
+
when :string, :int, :float
|
80
|
+
where[1]
|
81
|
+
when :prim
|
82
|
+
case where[1]
|
83
|
+
when nil
|
84
|
+
gen_word
|
85
|
+
when 0
|
86
|
+
rand(4711)
|
87
|
+
when 1
|
88
|
+
~rand(815)
|
89
|
+
when 2
|
90
|
+
gen_word.force_encoding(Encoding::BINARY)
|
91
|
+
when 3
|
92
|
+
gen_word
|
93
|
+
when 7
|
94
|
+
case where[2]
|
95
|
+
when nil
|
96
|
+
Math::PI
|
97
|
+
when 20
|
98
|
+
false
|
99
|
+
when 21
|
100
|
+
true
|
101
|
+
when 22
|
102
|
+
nil
|
103
|
+
when 23
|
104
|
+
:undefined
|
105
|
+
when 25, 26, 27
|
106
|
+
rand()
|
107
|
+
end
|
108
|
+
else
|
109
|
+
fail "Can't generate prim #{where[1]}"
|
110
|
+
end
|
111
|
+
else
|
112
|
+
# fail where
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
VALUE_TYPE = {string: true, int: true, float: true}
|
117
|
+
SIMPLE_VALUE = {
|
118
|
+
[:prim, 7, 20] => [true, false],
|
119
|
+
[:prim, 7, 21] => [true, true],
|
120
|
+
[:prim, 7, 22] => [true, nil],
|
121
|
+
[:prim, 7, 23] => [true, :undefined],
|
122
|
+
}
|
123
|
+
|
124
|
+
def extract_value(t)
|
125
|
+
if VALUE_TYPE[t[0]]
|
126
|
+
[true, t[1]]
|
127
|
+
elsif v = SIMPLE_VALUE[t]
|
128
|
+
v
|
129
|
+
else
|
130
|
+
[false]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def validate_diag
|
135
|
+
[@last_data, @last_rule, @last_message]
|
136
|
+
end
|
137
|
+
|
138
|
+
def validate(d, warn=true)
|
139
|
+
result = validate1(d)
|
140
|
+
unless result
|
141
|
+
if warn
|
142
|
+
warn "CDDL validation failure:"
|
143
|
+
PP::pp(validate_diag, STDERR)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
result
|
147
|
+
end
|
148
|
+
|
149
|
+
def validate_result(check)
|
150
|
+
check || (
|
151
|
+
@last_message = yield
|
152
|
+
false
|
153
|
+
)
|
154
|
+
end
|
155
|
+
|
156
|
+
def validate1(d, where=rules)
|
157
|
+
# puts "DATA: #{d.inspect}"
|
158
|
+
# puts "RULE: #{where.inspect}"
|
159
|
+
@last_data = d
|
160
|
+
@last_rule = where
|
161
|
+
case where[0]
|
162
|
+
when :type1
|
163
|
+
where[1..-1].any? {|r| validate1(d, r)}
|
164
|
+
when :map
|
165
|
+
if Hash === d
|
166
|
+
d_check = d.dup
|
167
|
+
where[1..-1].all? { |r|
|
168
|
+
# puts "SUBRULE: #{r.inspect}"
|
169
|
+
t, s, _e, k, v = r
|
170
|
+
fail unless t == :member
|
171
|
+
# this is mostly quadratic; let's do the linear thing if possible
|
172
|
+
fail "member name not known for group entry #{r} in map" unless k
|
173
|
+
simple, simpleval = extract_value(k)
|
174
|
+
if simple
|
175
|
+
# puts "SIMPLE: #{d_check.inspect} #{simpleval}"
|
176
|
+
# add occurrence check; check that val is present in the first place
|
177
|
+
actual = d.fetch(simpleval, :not_found)
|
178
|
+
if actual == :not_found
|
179
|
+
s == 0 # minimum occurrence must be 0 then
|
180
|
+
else
|
181
|
+
validate1(actual, v) && d_check.delete(simpleval)
|
182
|
+
end
|
183
|
+
else
|
184
|
+
# puts "COMPLEX: #{k.inspect} #{simple.inspect} #{simpleval.inspect}"
|
185
|
+
keys = d_check.keys
|
186
|
+
ta, keys = keys.partition{ |key| validate1(key, k)}
|
187
|
+
# XXX check ta.size against s/e
|
188
|
+
ta.all? { |val|
|
189
|
+
validate1(d[val], v) && d_check.delete(val)
|
190
|
+
}
|
191
|
+
end
|
192
|
+
} && d_check == {}
|
193
|
+
end
|
194
|
+
when :array
|
195
|
+
if Array === d
|
196
|
+
# validate1 against the record
|
197
|
+
i = 0
|
198
|
+
where[1..-1].each { |r|
|
199
|
+
t, s, e, _k, v = r # XXX
|
200
|
+
fail unless t == :member
|
201
|
+
occ = 0
|
202
|
+
while ((e == -1 || occ < e) && i != d.size && validate1(d[i], v))
|
203
|
+
i += 1
|
204
|
+
occ += 1
|
205
|
+
end
|
206
|
+
if occ < s
|
207
|
+
@last_message = "occur not reached in array #{d} for #{where}"
|
208
|
+
return false
|
209
|
+
end
|
210
|
+
}
|
211
|
+
validate_result(i == d.size) { "cannot complete array #{d} for #{where}" }
|
212
|
+
end
|
213
|
+
when :string, :int, :float
|
214
|
+
_, v = extract_value(where)
|
215
|
+
d == v
|
216
|
+
when :prim
|
217
|
+
case where[1]
|
218
|
+
when nil
|
219
|
+
true
|
220
|
+
when 0
|
221
|
+
Integer === d && d >= 0 && d <= 0xffffffffffffffff
|
222
|
+
when 1
|
223
|
+
Integer === d && d < 0 && d >= -0x10000000000000000
|
224
|
+
when 2
|
225
|
+
String === d && d.encoding == Encoding::BINARY
|
226
|
+
when 3
|
227
|
+
String === d && d.encoding != Encoding::BINARY # cheat
|
228
|
+
when 7
|
229
|
+
t, v = extract_value(where)
|
230
|
+
if t
|
231
|
+
v == d
|
232
|
+
else
|
233
|
+
case where[2]
|
234
|
+
when nil
|
235
|
+
# XXX
|
236
|
+
fail
|
237
|
+
when 25, 26, 27
|
238
|
+
Float === d
|
239
|
+
else
|
240
|
+
fail
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
else
|
245
|
+
fail "Can't generate prim #{where[1]}"
|
246
|
+
end
|
247
|
+
else
|
248
|
+
# fail where
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
|
253
|
+
attr_reader :ast
|
254
|
+
|
255
|
+
private
|
256
|
+
|
257
|
+
def gen_word
|
258
|
+
@words ||= File.read("/usr/share/dict/words").lines.shuffle
|
259
|
+
@wordptr ||= 0
|
260
|
+
@wordptr = 0 if @wordptr == @words.size
|
261
|
+
w = @words[@wordptr].chomp
|
262
|
+
@wordptr += 1
|
263
|
+
w
|
264
|
+
end
|
265
|
+
|
266
|
+
# XXX needs serious memoizing
|
267
|
+
|
268
|
+
def r_process(n, r)
|
269
|
+
t = r[0]
|
270
|
+
# puts "Processing rule #{n} = #{t}"
|
271
|
+
@stage1[n] ||= [t, *r[1..-1].map {|e| send(t, e)}]
|
272
|
+
end
|
273
|
+
|
274
|
+
def value(n)
|
275
|
+
# cheat:
|
276
|
+
val = eval(n.to_s)
|
277
|
+
[case val
|
278
|
+
when Integer; :int
|
279
|
+
when Numeric; :float
|
280
|
+
when String; :string
|
281
|
+
else fail "huh? value #{val.inspect}"
|
282
|
+
end, val]
|
283
|
+
end
|
284
|
+
|
285
|
+
def grpent(n) # returns array of entries
|
286
|
+
occ = occur(n.occur)
|
287
|
+
if mk = n.memberkey # work around unclear bug in ast generation
|
288
|
+
[[:member, *occ, label(mk.membername), type1(n.type1)]]
|
289
|
+
else
|
290
|
+
# fail n.inspect unless n.type1
|
291
|
+
t = if n.membername # work around unclear bug, part 2
|
292
|
+
type1(n.membername.type1, true)
|
293
|
+
else
|
294
|
+
type1(n.type1, true) # type1 can be a group here!
|
295
|
+
end
|
296
|
+
if t[0] == :grpent
|
297
|
+
# XXX go through the members here and multiply the occs
|
298
|
+
t1 = t[1..-1].flatten(1)
|
299
|
+
t1.flat_map {|t2|
|
300
|
+
if t2[0] == :member
|
301
|
+
[t2]
|
302
|
+
else
|
303
|
+
fail unless t2[0] == :grpent
|
304
|
+
t2[1..-1]
|
305
|
+
end
|
306
|
+
}.map {|mem|
|
307
|
+
# p mem
|
308
|
+
mem[1] *= occ[0]
|
309
|
+
mem[2] *= [occ[1], -1].max
|
310
|
+
# p mem
|
311
|
+
mem
|
312
|
+
}
|
313
|
+
else
|
314
|
+
[[:member, *occ, nil, t]]
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
def label(n)
|
320
|
+
# if this is not a rulename, it's a bareword
|
321
|
+
name = n.to_s
|
322
|
+
if r = @rules[name]
|
323
|
+
r_process(name, r)
|
324
|
+
else
|
325
|
+
[:string, name]
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
def type_recall(name, canbegroup)
|
330
|
+
if r = @rules[name]
|
331
|
+
t = r_process(name, r)
|
332
|
+
unless t[0] == :type1
|
333
|
+
fail "#{name} not a type" unless canbegroup && t[0] == :grpent
|
334
|
+
end
|
335
|
+
t
|
336
|
+
else
|
337
|
+
fail "Unknown type #{name}"
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
def group_recall(name)
|
342
|
+
if r = @rules[name]
|
343
|
+
g = r_process(name, r)
|
344
|
+
fail "#{name} not a group" unless g[0] == :grpent
|
345
|
+
g[1..-1]
|
346
|
+
else
|
347
|
+
fail "Unknown group #{name}"
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
BRACE = {"{" => :map, "[" => :array}
|
352
|
+
|
353
|
+
def type1(n, canbegroup = false)
|
354
|
+
# puts "NVALUE #{n.value.inspect}"
|
355
|
+
if v = n.value
|
356
|
+
value(n)
|
357
|
+
elsif v = n.typename
|
358
|
+
t = type_recall(v.to_s, canbegroup)
|
359
|
+
if t[0] == :type1
|
360
|
+
if t.size == 2
|
361
|
+
t = t[1]
|
362
|
+
else
|
363
|
+
t = [:type1, *t[1..-1].flat_map {|t1|
|
364
|
+
if t1[0] == :type1
|
365
|
+
t1[1..-1]
|
366
|
+
else
|
367
|
+
[t1]
|
368
|
+
end
|
369
|
+
}]
|
370
|
+
|
371
|
+
end
|
372
|
+
end # XXX should flatten the thing, too
|
373
|
+
t
|
374
|
+
else
|
375
|
+
case str = n.to_s
|
376
|
+
when "#"
|
377
|
+
[:prim]
|
378
|
+
when /\A#(\d+)/
|
379
|
+
maj = $1.to_i
|
380
|
+
s = [:prim, maj, *n.children(:uint).map(&:to_s).map(&:to_i)]
|
381
|
+
if tagged_type = n.type
|
382
|
+
s << type(tagged_type)
|
383
|
+
end
|
384
|
+
s
|
385
|
+
when /\A\(/
|
386
|
+
type(n.type)
|
387
|
+
when /\A[\[{]/
|
388
|
+
s = n.children(:group).flat_map {|ch| group(ch)}
|
389
|
+
type = BRACE[str[0]]
|
390
|
+
if type == :map
|
391
|
+
s.each do |member|
|
392
|
+
fail unless member[0] == :member
|
393
|
+
fail "map entry without member key given: #{member}" unless member[3]
|
394
|
+
end
|
395
|
+
end
|
396
|
+
[type, *s]
|
397
|
+
else
|
398
|
+
"unimplemented #{n}"
|
399
|
+
end
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
def type(n)
|
404
|
+
s = n.children(:type1).map {|ch| type1(ch)}
|
405
|
+
if s.size == 1
|
406
|
+
s.first
|
407
|
+
else
|
408
|
+
[:type1, *s]
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
def group(n) # returns array
|
413
|
+
n.children(:grpent).flat_map {|ch| grpent(ch)}
|
414
|
+
end
|
415
|
+
|
416
|
+
def occur(n)
|
417
|
+
case n.to_s
|
418
|
+
when ""
|
419
|
+
[1, 1]
|
420
|
+
when "+"
|
421
|
+
[1, -1]
|
422
|
+
when "?"
|
423
|
+
[0, 1]
|
424
|
+
when /\A(\d*)\*(\d*)\z/
|
425
|
+
if (s = $1) == ""
|
426
|
+
s = 0
|
427
|
+
else
|
428
|
+
s = s.to_i
|
429
|
+
end
|
430
|
+
if (e = $2) == ""
|
431
|
+
e = -1
|
432
|
+
else
|
433
|
+
e = e.to_i
|
434
|
+
end
|
435
|
+
[s, e]
|
436
|
+
else
|
437
|
+
fail "huh #{n.to_s}"
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
end
|
442
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
reputation-object = {
|
2
|
+
application: tstr
|
3
|
+
reputons: [* reputon]
|
4
|
+
}
|
5
|
+
|
6
|
+
reputon = {
|
7
|
+
rater: tstr
|
8
|
+
assertion: tstr
|
9
|
+
rated: tstr
|
10
|
+
rating: float16
|
11
|
+
? confidence: float16
|
12
|
+
? normal-rating: float16
|
13
|
+
? sample-size: uint
|
14
|
+
? generated: uint
|
15
|
+
? expires: uint
|
16
|
+
* any: any
|
17
|
+
}
|
@@ -0,0 +1,42 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
reputation-object = {
|
4
|
+
reputation-context,
|
5
|
+
reputon-list
|
6
|
+
}
|
7
|
+
|
8
|
+
reputation-context = (
|
9
|
+
application: tstr
|
10
|
+
)
|
11
|
+
|
12
|
+
reputon-list = (
|
13
|
+
reputons: reputon-array
|
14
|
+
)
|
15
|
+
|
16
|
+
reputon-array = [* reputon]
|
17
|
+
|
18
|
+
reputon = {
|
19
|
+
rater-value,
|
20
|
+
assertion-value,
|
21
|
+
rated-value,
|
22
|
+
rating-value,
|
23
|
+
? conf-value,
|
24
|
+
? normal-value,
|
25
|
+
? sample-value,
|
26
|
+
? gen-value,
|
27
|
+
? expire-value,
|
28
|
+
* ext-value,
|
29
|
+
}
|
30
|
+
|
31
|
+
rater-value = ( rater: tstr )
|
32
|
+
assertion-value = ( assertion: tstr )
|
33
|
+
rated-value = ( rated: tstr )
|
34
|
+
rating-value = ( rating: float16 )
|
35
|
+
conf-value = ( confidence: float16 )
|
36
|
+
normal-value = ( normal-rating: float16 )
|
37
|
+
sample-value = ( sample-size: uint )
|
38
|
+
gen-value = ( generated: uint )
|
39
|
+
expire-value = ( expires: uint )
|
40
|
+
ext-value = ( any: any )
|
41
|
+
|
42
|
+
|
data/test/test-cddl.rb
ADDED
@@ -0,0 +1,292 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
require 'cbor-pretty.rb'
|
5
|
+
require 'cbor-diagnostic.rb'
|
6
|
+
|
7
|
+
require_relative '../lib/cddl'
|
8
|
+
|
9
|
+
|
10
|
+
class TestABNF < Test::Unit::TestCase
|
11
|
+
|
12
|
+
TEST_DATA_DIR = Pathname.new(__FILE__).split[0] + '../test-data'
|
13
|
+
|
14
|
+
EXPECTED_RULES = [:type1,
|
15
|
+
[:map,
|
16
|
+
[:member, 1, 1, [:string, "application"], [:prim, 3]],
|
17
|
+
[:member,
|
18
|
+
1,
|
19
|
+
1,
|
20
|
+
[:string, "reputons"],
|
21
|
+
[:array,
|
22
|
+
[:member,
|
23
|
+
0,
|
24
|
+
-1,
|
25
|
+
nil,
|
26
|
+
[:map,
|
27
|
+
[:member, 1, 1, [:string, "rater"], [:prim, 3]],
|
28
|
+
[:member, 1, 1, [:string, "assertion"], [:prim, 3]],
|
29
|
+
[:member, 1, 1, [:string, "rated"], [:prim, 3]],
|
30
|
+
[:member, 1, 1, [:string, "rating"], [:prim, 7, 25]],
|
31
|
+
[:member, 0, 1, [:string, "confidence"], [:prim, 7, 25]],
|
32
|
+
[:member, 0, 1, [:string, "normal-rating"], [:prim, 7, 25]],
|
33
|
+
[:member, 0, 1, [:string, "sample-size"], [:prim, 0]],
|
34
|
+
[:member, 0, 1, [:string, "generated"], [:prim, 0]],
|
35
|
+
[:member, 0, 1, [:string, "expires"], [:prim, 0]],
|
36
|
+
[:member, 0, -1, [:type1, [:prim]], [:prim]]]]]]]]
|
37
|
+
|
38
|
+
def test_aa_rfc7071_concise
|
39
|
+
parser1 = CDDL::Parser.new(File.read("#{TEST_DATA_DIR}/7071-concise.cddl"))
|
40
|
+
assert_equal EXPECTED_RULES, parser1.rules
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_aa_rfc7071_verbose
|
44
|
+
parser2 = CDDL::Parser.new(File.read("#{TEST_DATA_DIR}/7071-verbose.cddl"))
|
45
|
+
assert_equal EXPECTED_RULES, parser2.rules
|
46
|
+
3.times do
|
47
|
+
# puts CBOR::pretty(CBOR::encode(parser2.generate(EXPECTED_RULES)))
|
48
|
+
puts parser2.generate.cbor_diagnostic
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_validate
|
53
|
+
parser1 = CDDL::Parser.new(File.read("test-data/7071-concise.cddl"))
|
54
|
+
g = parser1.generate
|
55
|
+
pp g
|
56
|
+
assert parser1.validate(g)
|
57
|
+
old = g["application"]
|
58
|
+
g["application"] = 4711
|
59
|
+
refute parser1.validate(g, false)
|
60
|
+
g["application"] = old
|
61
|
+
g["reputons"] << 5712
|
62
|
+
refute parser1.validate(g, false)
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_validate_1
|
66
|
+
parser = CDDL::Parser.new <<HERE
|
67
|
+
test = 1
|
68
|
+
HERE
|
69
|
+
assert parser.validate(1)
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_validate_a
|
73
|
+
parser = CDDL::Parser.new <<HERE
|
74
|
+
test = [* one: 1]
|
75
|
+
HERE
|
76
|
+
assert parser.validate([])
|
77
|
+
assert parser.validate([1])
|
78
|
+
assert parser.validate([1, 1])
|
79
|
+
refute parser.validate([1, 2], false)
|
80
|
+
refute parser.validate([2, 1], false)
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_validate_a_string
|
84
|
+
parser = CDDL::Parser.new <<HERE
|
85
|
+
test = [* one: "one"]
|
86
|
+
HERE
|
87
|
+
assert parser.validate([])
|
88
|
+
assert parser.validate(["one"])
|
89
|
+
assert parser.validate(["one", "one"])
|
90
|
+
refute parser.validate([1], false)
|
91
|
+
refute parser.validate(['two'], false)
|
92
|
+
refute parser.validate(["one", "two"], false)
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
def test_validate_a_string_string
|
97
|
+
parser = CDDL::Parser.new <<HERE
|
98
|
+
test = [* "two": "one"]
|
99
|
+
HERE
|
100
|
+
assert parser.validate([])
|
101
|
+
assert parser.validate(["one"])
|
102
|
+
assert parser.validate(["one", "one"])
|
103
|
+
refute parser.validate([1], false)
|
104
|
+
refute parser.validate(['two'], false)
|
105
|
+
refute parser.validate(["one", "two"], false)
|
106
|
+
end
|
107
|
+
|
108
|
+
def test_validate_a_once
|
109
|
+
# $debug_ast = true
|
110
|
+
parser = CDDL::Parser.new <<HERE
|
111
|
+
test = [1]
|
112
|
+
HERE
|
113
|
+
# puts "RULES:"
|
114
|
+
# pp parser.rules
|
115
|
+
# puts "APR:"
|
116
|
+
# pp parser.apr
|
117
|
+
refute parser.validate([], false)
|
118
|
+
assert parser.validate([1])
|
119
|
+
refute parser.validate([1, 1], false)
|
120
|
+
refute parser.validate([1, 2], false)
|
121
|
+
refute parser.validate([2, 1], false)
|
122
|
+
end
|
123
|
+
|
124
|
+
def test_validate_unknown_key
|
125
|
+
# $debug_ast = true
|
126
|
+
parser = CDDL::Parser.new <<HERE
|
127
|
+
test = { 1, 2 }
|
128
|
+
HERE
|
129
|
+
# puts "RULES:"
|
130
|
+
assert_raise { # TODO: This really should be checked at parse time
|
131
|
+
pp parser.rules
|
132
|
+
}
|
133
|
+
# puts "APR:"
|
134
|
+
# pp parser.apr
|
135
|
+
assert_raise { puts parser.generate() }
|
136
|
+
assert_raise { parser.validate({}) }
|
137
|
+
end
|
138
|
+
|
139
|
+
def test_validate_not_unknown_key
|
140
|
+
# $debug_ast = true
|
141
|
+
parser = CDDL::Parser.new <<HERE
|
142
|
+
test = [ 1, 2 ]
|
143
|
+
HERE
|
144
|
+
# puts "RULES:"
|
145
|
+
# pp parser.rules
|
146
|
+
# puts "APR:"
|
147
|
+
# pp parser.apr
|
148
|
+
assert_equal [1, 2], parser.generate()
|
149
|
+
refute parser.validate({}, false)
|
150
|
+
refute parser.validate([], false)
|
151
|
+
refute parser.validate([1], false)
|
152
|
+
assert parser.validate([1, 2])
|
153
|
+
end
|
154
|
+
|
155
|
+
|
156
|
+
def test_validate_not_unknown_key_paren
|
157
|
+
# $debug_ast = true
|
158
|
+
parser = CDDL::Parser.new <<HERE
|
159
|
+
test = [ (1, 2) ]
|
160
|
+
HERE
|
161
|
+
# puts "RULES:"
|
162
|
+
# pp parser.rules
|
163
|
+
# puts "APR:"
|
164
|
+
# pp parser.apr
|
165
|
+
assert_equal [1, 2], parser.generate()
|
166
|
+
refute parser.validate({}, false)
|
167
|
+
refute parser.validate([], false)
|
168
|
+
refute parser.validate([1], false)
|
169
|
+
assert parser.validate([1, 2])
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
def test_validate_alternate3 # XXX need indirection for now
|
174
|
+
# $debug_ast = true
|
175
|
+
parser = CDDL::Parser.new <<HERE
|
176
|
+
test = [* test1]
|
177
|
+
test1 = (one: 1, two: 2)
|
178
|
+
HERE
|
179
|
+
# puts "RULES:"
|
180
|
+
# pp parser.rules
|
181
|
+
# puts "APR:"
|
182
|
+
# pp parser.apr
|
183
|
+
assert parser.validate([])
|
184
|
+
assert parser.validate([1, 2])
|
185
|
+
assert parser.validate([1, 1, 2, 2]) # XXX: This is probably a BUG
|
186
|
+
refute parser.validate([1, 2, 1, 2], false) # XXX->assert: major surgery required
|
187
|
+
assert parser.validate([1]) # XXX -> refute (rules left)
|
188
|
+
refute parser.validate([1, 2, 1], false)
|
189
|
+
end
|
190
|
+
|
191
|
+
def test_validate_occur1 # XXX need indirection for now
|
192
|
+
# $debug_ast = true
|
193
|
+
parser = CDDL::Parser.new <<HERE
|
194
|
+
test = [test1]
|
195
|
+
test1 = (one: 1, two: 2)
|
196
|
+
HERE
|
197
|
+
# puts "RULES:"
|
198
|
+
# pp parser.rules
|
199
|
+
# puts "APR:"
|
200
|
+
# pp parser.apr
|
201
|
+
refute parser.validate([], false)
|
202
|
+
refute parser.validate([1], false)
|
203
|
+
assert parser.validate([1, 2])
|
204
|
+
refute parser.validate([2, 1], false)
|
205
|
+
refute parser.validate([1, 1, 2, 2], false)
|
206
|
+
refute parser.validate([1, 2, 1, 2], false)
|
207
|
+
refute parser.validate([1, 2, 1], false)
|
208
|
+
end
|
209
|
+
|
210
|
+
def test_validate_occur01 # XXX need indirection for now
|
211
|
+
# $debug_ast = true
|
212
|
+
parser = CDDL::Parser.new <<HERE
|
213
|
+
test = [? test1]
|
214
|
+
test1 = (one: 1, two: 2)
|
215
|
+
HERE
|
216
|
+
# puts "RULES:"
|
217
|
+
# pp parser.rules
|
218
|
+
# puts "APR:"
|
219
|
+
# pp parser.apr
|
220
|
+
assert parser.validate([])
|
221
|
+
assert parser.validate([1])
|
222
|
+
assert parser.validate([1, 2])
|
223
|
+
refute parser.validate([2, 1], false)
|
224
|
+
refute parser.validate([1, 1, 2, 2], false)
|
225
|
+
refute parser.validate([1, 2, 1, 2], false)
|
226
|
+
refute parser.validate([1, 2, 1], false)
|
227
|
+
end
|
228
|
+
|
229
|
+
def test_validate_occur1n # XXX need indirection for now
|
230
|
+
# $debug_ast = true
|
231
|
+
parser = CDDL::Parser.new <<HERE
|
232
|
+
test = [+ test1]
|
233
|
+
test1 = (one: 1, two: 2)
|
234
|
+
HERE
|
235
|
+
# puts "RULES:"
|
236
|
+
# pp parser.rules
|
237
|
+
# puts "APR:"
|
238
|
+
# pp parser.apr
|
239
|
+
refute parser.validate([], false)
|
240
|
+
refute parser.validate([1], false)
|
241
|
+
assert parser.validate([1, 2])
|
242
|
+
refute parser.validate([2, 1], false)
|
243
|
+
assert parser.validate([1, 1, 2, 2]) # XXX not sequence preserving
|
244
|
+
refute parser.validate([1, 2, 1, 2], false)
|
245
|
+
refute parser.validate([1, 2, 1], false)
|
246
|
+
end
|
247
|
+
|
248
|
+
def test_validate_occur0n # XXX need indirection for now
|
249
|
+
# $debug_ast = true
|
250
|
+
parser = CDDL::Parser.new <<HERE
|
251
|
+
test = [* test1]
|
252
|
+
test1 = (one: 1, two: 2)
|
253
|
+
HERE
|
254
|
+
# puts "RULES:"
|
255
|
+
# pp parser.rules
|
256
|
+
# puts "APR:"
|
257
|
+
# pp parser.apr
|
258
|
+
assert parser.validate([])
|
259
|
+
assert parser.validate([1])
|
260
|
+
assert parser.validate([1, 2])
|
261
|
+
refute parser.validate([2, 1], false)
|
262
|
+
assert parser.validate([1, 1, 2, 2]) # XXX not sequence preserving
|
263
|
+
refute parser.validate([1, 2, 1, 2], false)
|
264
|
+
refute parser.validate([1, 2, 1], false)
|
265
|
+
end
|
266
|
+
|
267
|
+
|
268
|
+
def test_validate_occur23 # XXX need indirection for now
|
269
|
+
# $debug_ast = true
|
270
|
+
parser = CDDL::Parser.new <<HERE
|
271
|
+
test = [2*3 test1]
|
272
|
+
test1 = (one: 1, two: 2)
|
273
|
+
HERE
|
274
|
+
# puts "RULES:"
|
275
|
+
# pp parser.rules
|
276
|
+
# puts "APR:"
|
277
|
+
# pp parser.apr
|
278
|
+
pp parser.generate
|
279
|
+
refute parser.validate([], false)
|
280
|
+
refute parser.validate([1], false)
|
281
|
+
refute parser.validate([1, 2], false)
|
282
|
+
refute parser.validate([2, 1], false)
|
283
|
+
assert parser.validate([1, 1, 2, 2]) # XXX not sequence preserving
|
284
|
+
assert parser.validate([1, 1, 1, 2, 2]) # XXX not sequence preserving
|
285
|
+
assert parser.validate([1, 1, 2, 2, 2]) # XXX not sequence preserving
|
286
|
+
assert parser.validate([1, 1, 1, 2, 2, 2]) # XXX not sequence preserving
|
287
|
+
refute parser.validate([1, 2, 1, 2], false)
|
288
|
+
refute parser.validate([1, 2, 1], false)
|
289
|
+
end
|
290
|
+
|
291
|
+
|
292
|
+
end
|
metadata
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cddl
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Carsten Bormann
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-02-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: cbor-diag
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: abnc
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: json
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: A parser, generator, and validator for CDDL
|
56
|
+
email: cabo@tzi.org
|
57
|
+
executables:
|
58
|
+
- cddl
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- bin/cddl
|
63
|
+
- cddl.gemspec
|
64
|
+
- data/cddl.abnf
|
65
|
+
- data/prelude.cddl
|
66
|
+
- lib/cddl.rb
|
67
|
+
- test-data/7071-concise.cddl
|
68
|
+
- test-data/7071-verbose.cddl
|
69
|
+
- test/test-cddl.rb
|
70
|
+
homepage: http://github.com/cabo/cddl
|
71
|
+
licenses:
|
72
|
+
- MIT
|
73
|
+
metadata: {}
|
74
|
+
post_install_message:
|
75
|
+
rdoc_options: []
|
76
|
+
require_paths:
|
77
|
+
- lib
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 1.9.2
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
requirements: []
|
89
|
+
rubyforge_project:
|
90
|
+
rubygems_version: 2.4.5
|
91
|
+
signing_key:
|
92
|
+
specification_version: 4
|
93
|
+
summary: CDDL generator and validator.
|
94
|
+
test_files: []
|
95
|
+
has_rdoc:
|