cddl 0.1.0
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/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:
|