testml 0.0.1 → 0.0.2
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/.gemspec +3 -1
- data/CHANGELOG.yaml +4 -1
- data/LICENSE +1 -1
- data/README.rdoc +30 -5
- data/Rakefile +10 -1
- data/ToDo +56 -0
- data/lib/rake/testml.rb +14 -0
- data/lib/testml.rb +46 -2
- data/lib/testml/bridge.rb +5 -0
- data/lib/testml/compiler.rb +106 -0
- data/lib/testml/compiler/lite.rb +194 -0
- data/lib/testml/compiler/pegex.rb +50 -0
- data/lib/testml/compiler/pegex/ast.rb +145 -0
- data/lib/testml/compiler/pegex/grammar.rb +173 -0
- data/lib/testml/library.rb +7 -0
- data/lib/testml/library/debug.rb +18 -0
- data/lib/testml/library/standard.rb +86 -0
- data/lib/testml/runtime.rb +501 -0
- data/lib/testml/runtime/unit.rb +94 -0
- data/lib/testml/setup.rb +65 -0
- data/lib/testml/util.rb +22 -0
- data/test/ast/arguments.tml +87 -0
- data/test/ast/basic.tml +83 -0
- data/test/ast/dataless.tml +36 -0
- data/test/ast/exceptions.tml +59 -0
- data/test/ast/external.tml +42 -0
- data/test/ast/function.tml +276 -0
- data/test/ast/label.tml +58 -0
- data/test/ast/markers.tml +36 -0
- data/test/ast/semicolons.tml +30 -0
- data/test/ast/truth.tml +85 -0
- data/test/ast/types.tml +163 -0
- data/test/compile-lite.rb +38 -0
- data/test/compile-testml-document.rb +59 -0
- data/test/compile.rb +57 -0
- data/test/inline-bridge.rb +30 -0
- data/test/inline.rb +28 -0
- data/test/strings.rb +24 -0
- data/test/testml.rb +38 -0
- data/test/testml.yaml +10 -0
- data/test/testml/arguments.tml +18 -0
- data/test/testml/assertions.tml +15 -0
- data/test/testml/basic.tml +37 -0
- data/test/testml/dataless.tml +9 -0
- data/test/testml/exceptions.tml +16 -0
- data/test/testml/external.tml +8 -0
- data/test/testml/external1.tml +10 -0
- data/test/testml/external2.tml +3 -0
- data/test/testml/function.tml +82 -0
- data/test/testml/label.tml +24 -0
- data/test/testml/markers.tml +19 -0
- data/test/testml/semicolons.tml +10 -0
- data/test/testml/standard.tml +50 -0
- data/test/testml/truth.tml +22 -0
- data/test/testml/types.tml +24 -0
- data/test/testml_bridge.rb +28 -0
- metadata +69 -4
- data/test/fail.rb +0 -12
@@ -0,0 +1,501 @@
|
|
1
|
+
class TestML;end
|
2
|
+
|
3
|
+
class TestML::Runtime
|
4
|
+
attr_accessor :testml
|
5
|
+
attr_accessor :bridge
|
6
|
+
attr_accessor :library
|
7
|
+
attr_accessor :compiler
|
8
|
+
attr_accessor :skip
|
9
|
+
|
10
|
+
attr_accessor :function
|
11
|
+
attr_accessor :error
|
12
|
+
attr_accessor :global
|
13
|
+
attr_accessor :base
|
14
|
+
|
15
|
+
def initialize(attributes={})
|
16
|
+
attributes.each { |k,v| self.send "#{k}=", v }
|
17
|
+
$TestMLRuntimeSingleton = self
|
18
|
+
@base ||= 'test'
|
19
|
+
end
|
20
|
+
|
21
|
+
def run
|
22
|
+
compile_testml
|
23
|
+
initialize_runtime
|
24
|
+
run_function(@function, [])
|
25
|
+
end
|
26
|
+
|
27
|
+
def run_function(function, args)
|
28
|
+
signature = apply_signature(function, args)
|
29
|
+
|
30
|
+
parent = @function
|
31
|
+
@function = function
|
32
|
+
|
33
|
+
function.statements.each do |statement|
|
34
|
+
if statement.kind_of? TestML::Assignment
|
35
|
+
run_assignment(statement)
|
36
|
+
else
|
37
|
+
run_statement(statement)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
@function = parent
|
42
|
+
return
|
43
|
+
end
|
44
|
+
|
45
|
+
def apply_signature(function, args)
|
46
|
+
signature = function.signature || []
|
47
|
+
|
48
|
+
fail "Function received #{args.size} args but expected #{signature.size}" \
|
49
|
+
if ! signature.empty? and args.size != signature.size
|
50
|
+
|
51
|
+
@function.setvar('Self', function)
|
52
|
+
signature.each_with_index do |sig_elem, i|
|
53
|
+
arg = args[i]
|
54
|
+
arg = run_expression(arg) \
|
55
|
+
if arg.kind_of? TestML::Expression
|
56
|
+
function.setvar(sig_elem, arg)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def run_statement(statement)
|
61
|
+
blocks = select_blocks(statement.points || [])
|
62
|
+
blocks.each do |block|
|
63
|
+
@function.setvar('Block', block) if block != 1
|
64
|
+
|
65
|
+
result = run_expression(statement.expr)
|
66
|
+
if assertion = statement.assert
|
67
|
+
run_assertion(result, assertion)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def run_assignment(assignment)
|
73
|
+
@function.setvar(
|
74
|
+
assignment.name,
|
75
|
+
run_expression(assignment.expr)
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
def run_assertion left, assert
|
80
|
+
method_ = method(('assert_' + assert.name).to_sym)
|
81
|
+
|
82
|
+
@function.getvar('TestNumber').value += 1
|
83
|
+
|
84
|
+
if assert.expr
|
85
|
+
method_.call(left, run_expression(assert.expr))
|
86
|
+
else
|
87
|
+
method_.call(left)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def run_expression(expr)
|
92
|
+
context = nil
|
93
|
+
@error = nil
|
94
|
+
if expr.kind_of? TestML::Expression
|
95
|
+
calls = expr.calls.clone
|
96
|
+
fail if calls.size <= 1
|
97
|
+
context = run_call(calls.shift)
|
98
|
+
calls.each do |call|
|
99
|
+
if @error
|
100
|
+
next unless
|
101
|
+
call.kind_of?(TestML::Call) and
|
102
|
+
call.name == 'Catch'
|
103
|
+
end
|
104
|
+
context = run_call(call, context)
|
105
|
+
end
|
106
|
+
else
|
107
|
+
context = run_call(expr)
|
108
|
+
end
|
109
|
+
if @error
|
110
|
+
fail @error
|
111
|
+
end
|
112
|
+
return context
|
113
|
+
end
|
114
|
+
|
115
|
+
def run_call call, context=nil
|
116
|
+
if call.kind_of? TestML::Object
|
117
|
+
return call
|
118
|
+
end
|
119
|
+
if call.kind_of? TestML::Function
|
120
|
+
return call
|
121
|
+
end
|
122
|
+
if call.kind_of? TestML::Point
|
123
|
+
return get_point(call.name)
|
124
|
+
end
|
125
|
+
if call.kind_of? TestML::Call
|
126
|
+
name = call.name
|
127
|
+
callable =
|
128
|
+
@function.getvar(name) ||
|
129
|
+
get_point(name) ||
|
130
|
+
lookup_callable(name) ||
|
131
|
+
fail("Can't locate '#{name}' callable")
|
132
|
+
if callable.kind_of? TestML::Object
|
133
|
+
return callable
|
134
|
+
end
|
135
|
+
return callable unless call.args or !context.nil?
|
136
|
+
call.args ||= []
|
137
|
+
args = call.args.map{|arg| run_expression(arg)}
|
138
|
+
args.unshift context if !context.nil?
|
139
|
+
if callable.kind_of? TestML::Callable
|
140
|
+
begin
|
141
|
+
value = callable.value.call(*args)
|
142
|
+
rescue
|
143
|
+
@error = $!.message
|
144
|
+
# @error = "#{$!.class}: #{$!.message}\n at #{$!.backtrace[0]}"
|
145
|
+
return TestML::Error.new(@error)
|
146
|
+
end
|
147
|
+
fail "'#{name}' did not return a TestML::Object object" \
|
148
|
+
unless value.kind_of? TestML::Object
|
149
|
+
return value
|
150
|
+
end
|
151
|
+
if callable.kind_of? TestML::Function
|
152
|
+
return run_function(callable, args)
|
153
|
+
end
|
154
|
+
fail
|
155
|
+
end
|
156
|
+
fail
|
157
|
+
end
|
158
|
+
|
159
|
+
def lookup_callable(name)
|
160
|
+
@function.getvar('Library').value.each do |library|
|
161
|
+
if library.respond_to?(name)
|
162
|
+
function = lambda do |*args|
|
163
|
+
library.method(name).call(*args)
|
164
|
+
end
|
165
|
+
callable = TestML::Callable.new(function)
|
166
|
+
@function.setvar(name, callable)
|
167
|
+
return callable
|
168
|
+
end
|
169
|
+
end
|
170
|
+
return nil
|
171
|
+
end
|
172
|
+
|
173
|
+
def get_point(name)
|
174
|
+
value = @function.getvar('Block').points[name] or return
|
175
|
+
if value.sub!(/\n+\z/, "\n") and value == "\n"
|
176
|
+
value = ''
|
177
|
+
end
|
178
|
+
return TestML::Str.new(value)
|
179
|
+
end
|
180
|
+
|
181
|
+
def select_blocks(wanted)
|
182
|
+
return [1] if wanted.empty?
|
183
|
+
selected = []
|
184
|
+
@function.data.each do |block|
|
185
|
+
points = block.points
|
186
|
+
next if points.key?('SKIP')
|
187
|
+
next unless wanted.all?{|point| points.key?(point)}
|
188
|
+
if points.key?('ONLY')
|
189
|
+
selected = [block]
|
190
|
+
break
|
191
|
+
end
|
192
|
+
selected << block
|
193
|
+
break if points.key?('LAST')
|
194
|
+
end
|
195
|
+
return selected
|
196
|
+
end
|
197
|
+
|
198
|
+
def compile_testml
|
199
|
+
fail "'testml' document required but not found" \
|
200
|
+
unless @testml
|
201
|
+
if @testml !~ /\n/
|
202
|
+
@testml =~ /(.*)\/(.*)/ or fail
|
203
|
+
testml = $2
|
204
|
+
@base = @base + '/' + $1
|
205
|
+
@testml = read_testml_file testml
|
206
|
+
end
|
207
|
+
@function = @compiler.new.compile(@testml)
|
208
|
+
end
|
209
|
+
|
210
|
+
def initialize_runtime
|
211
|
+
@global = @function.outer
|
212
|
+
|
213
|
+
@global.setvar('Block', TestML::Block.new)
|
214
|
+
@global.setvar('Label', TestML::Str.new('$BlockLabel'))
|
215
|
+
@global.setvar('True', TestML::Constant::True)
|
216
|
+
@global.setvar('False', TestML::Constant::False)
|
217
|
+
@global.setvar('None', TestML::Constant::None)
|
218
|
+
@global.setvar('TestNumber', TestML::Num.new(0))
|
219
|
+
@global.setvar('Library', TestML::List.new)
|
220
|
+
|
221
|
+
library = @function.getvar('Library')
|
222
|
+
[@bridge, @library].each do |lib|
|
223
|
+
if lib.kind_of? Array
|
224
|
+
lib.each {|l| library.push(l.new)}
|
225
|
+
else
|
226
|
+
library.push(lib.new)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def get_label
|
232
|
+
label = @function.getvar('Label') or return
|
233
|
+
label = label.value
|
234
|
+
label.gsub(/\$(\w+)/) {|m| replace_label($1)}
|
235
|
+
end
|
236
|
+
|
237
|
+
def replace_label(var)
|
238
|
+
block = @function.getvar('Block')
|
239
|
+
if var == 'BlockLabel'
|
240
|
+
block.label
|
241
|
+
elsif v = block.points[var]
|
242
|
+
v.sub!(/\n.*/m, '')
|
243
|
+
v.strip
|
244
|
+
elsif v = function.getvar(var)
|
245
|
+
v.value
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
def read_testml_file file
|
250
|
+
path = @base + '/' + file
|
251
|
+
File.read(path)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
#-----------------------------------------------------------------------------
|
256
|
+
class TestML::Function
|
257
|
+
attr_accessor :signature
|
258
|
+
attr_accessor :statements
|
259
|
+
attr_accessor :namespace
|
260
|
+
attr_accessor :data
|
261
|
+
|
262
|
+
@@outer = {}
|
263
|
+
def outer; @@outer[self.object_id] end
|
264
|
+
def outer=(value); @@outer[self.object_id] = value end
|
265
|
+
def type; 'Func' end
|
266
|
+
def namespace; @namespace ||= {} end
|
267
|
+
|
268
|
+
def initialize
|
269
|
+
@statements = []
|
270
|
+
end
|
271
|
+
|
272
|
+
def getvar name
|
273
|
+
s = self
|
274
|
+
while s
|
275
|
+
if s.namespace.key? name
|
276
|
+
return s.namespace[name]
|
277
|
+
end
|
278
|
+
s = s.outer
|
279
|
+
end
|
280
|
+
nil
|
281
|
+
end
|
282
|
+
|
283
|
+
def setvar name, value
|
284
|
+
namespace[name] = value
|
285
|
+
end
|
286
|
+
|
287
|
+
def forgetvar name
|
288
|
+
namespace.delete name
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
#-----------------------------------------------------------------------------
|
293
|
+
class TestML::Assignment
|
294
|
+
attr_accessor :name
|
295
|
+
attr_accessor :expr
|
296
|
+
|
297
|
+
def initialize(name, expr)
|
298
|
+
@name = name
|
299
|
+
@expr = expr
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
#-----------------------------------------------------------------------------
|
304
|
+
class TestML::Statement
|
305
|
+
attr_accessor :expr
|
306
|
+
attr_accessor :assert
|
307
|
+
attr_accessor :points
|
308
|
+
|
309
|
+
def initialize(expr, assert=nil, points=nil)
|
310
|
+
@expr = expr
|
311
|
+
@assert = assert if assert
|
312
|
+
@points = points if points
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
#-----------------------------------------------------------------------------
|
317
|
+
class TestML::Expression
|
318
|
+
attr_accessor :calls
|
319
|
+
|
320
|
+
def initialize(calls=[])
|
321
|
+
@calls = calls
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
#-----------------------------------------------------------------------------
|
326
|
+
class TestML::Assertion
|
327
|
+
attr_accessor :name
|
328
|
+
attr_accessor :expr
|
329
|
+
|
330
|
+
def initialize(name, expr=nil)
|
331
|
+
@name = name
|
332
|
+
@expr = expr if expr
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
#-----------------------------------------------------------------------------
|
337
|
+
class TestML::Call
|
338
|
+
attr_accessor :name
|
339
|
+
attr_accessor :args
|
340
|
+
|
341
|
+
def initialize(name, args=[])
|
342
|
+
@name = name
|
343
|
+
@args = args if !args.empty?
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
#-----------------------------------------------------------------------------
|
348
|
+
class TestML::Callable
|
349
|
+
attr_accessor :value
|
350
|
+
|
351
|
+
def initialize(value)
|
352
|
+
@value = value
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
#-----------------------------------------------------------------------------
|
357
|
+
class TestML::Block
|
358
|
+
attr_accessor :label
|
359
|
+
attr_accessor :points
|
360
|
+
|
361
|
+
def initialize(label='', points={})
|
362
|
+
@label = label
|
363
|
+
@points = points
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
#-----------------------------------------------------------------------------
|
368
|
+
class TestML::Point
|
369
|
+
attr_accessor :name
|
370
|
+
|
371
|
+
def initialize(name)
|
372
|
+
@name = name
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
#-----------------------------------------------------------------------------
|
377
|
+
class TestML::Object
|
378
|
+
attr_accessor :value
|
379
|
+
|
380
|
+
def initialize(value)
|
381
|
+
@value = value
|
382
|
+
end
|
383
|
+
|
384
|
+
def type
|
385
|
+
type = self.class.to_s
|
386
|
+
type.sub! /^TestML::/, '' or fail "Can't find type of '#{type}'"
|
387
|
+
return type
|
388
|
+
end
|
389
|
+
|
390
|
+
def str
|
391
|
+
fail "Cast from #{type} to Str is not supported"
|
392
|
+
end
|
393
|
+
def num
|
394
|
+
fail "Cast from #{type} to Num is not supported"
|
395
|
+
end
|
396
|
+
def bool
|
397
|
+
fail "Cast from #{type} to Bool is not supported"
|
398
|
+
end
|
399
|
+
def list
|
400
|
+
fail "Cast from #{type} to List is not supported"
|
401
|
+
end
|
402
|
+
def none
|
403
|
+
TestML::Constant::None
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
#-----------------------------------------------------------------------------
|
408
|
+
class TestML::Str < TestML::Object
|
409
|
+
def initialize(str)
|
410
|
+
@value = str.to_s
|
411
|
+
end
|
412
|
+
def str
|
413
|
+
self
|
414
|
+
end
|
415
|
+
def num
|
416
|
+
TestML::Num.new(@value =~ /^-?\d+(?:\.\d+)$/ ? $1.to_i : 0)
|
417
|
+
end
|
418
|
+
def bool
|
419
|
+
!@value.empty? ? TestML::Constant::True : TestML::Constant::False
|
420
|
+
end
|
421
|
+
def list
|
422
|
+
TestML::List.new(@value.split //)
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
#-----------------------------------------------------------------------------
|
427
|
+
class TestML::Num < TestML::Object
|
428
|
+
def str
|
429
|
+
TestML::Str.new(@value.to_s)
|
430
|
+
end
|
431
|
+
def num
|
432
|
+
self
|
433
|
+
end
|
434
|
+
def bool
|
435
|
+
@value != 0 ? TestML::Constant::True : TestML::Constant::False
|
436
|
+
end
|
437
|
+
def list
|
438
|
+
list = []
|
439
|
+
[1..(@value-1)].each { |i| list[i - 1] = nil }
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
#-----------------------------------------------------------------------------
|
444
|
+
class TestML::Bool < TestML::Object
|
445
|
+
def str
|
446
|
+
TestML::Str.new(@value ? "1" : "")
|
447
|
+
end
|
448
|
+
def num
|
449
|
+
TestML::Num.new(@value ? 1 : 0)
|
450
|
+
end
|
451
|
+
def bool
|
452
|
+
self
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
#-----------------------------------------------------------------------------
|
457
|
+
class TestML::List < TestML::Object
|
458
|
+
def initialize(value=[])
|
459
|
+
super(value)
|
460
|
+
end
|
461
|
+
def push elem
|
462
|
+
@value.push elem
|
463
|
+
end
|
464
|
+
def list
|
465
|
+
return self
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
#-----------------------------------------------------------------------------
|
470
|
+
class TestML::None < TestML::Object
|
471
|
+
def initialize
|
472
|
+
super(nil)
|
473
|
+
end
|
474
|
+
def str
|
475
|
+
TestML::Str.new('')
|
476
|
+
end
|
477
|
+
def num
|
478
|
+
TestML::Num.new(0)
|
479
|
+
end
|
480
|
+
def bool
|
481
|
+
TestML::Constant::False
|
482
|
+
end
|
483
|
+
def list
|
484
|
+
TestML::List.new []
|
485
|
+
end
|
486
|
+
end
|
487
|
+
|
488
|
+
#-----------------------------------------------------------------------------
|
489
|
+
class TestML::Native < TestML::Object
|
490
|
+
end
|
491
|
+
|
492
|
+
#-----------------------------------------------------------------------------
|
493
|
+
class TestML::Error < TestML::Object
|
494
|
+
end
|
495
|
+
|
496
|
+
#-----------------------------------------------------------------------------
|
497
|
+
module TestML::Constant
|
498
|
+
True = TestML::Bool.new(true)
|
499
|
+
False = TestML::Bool.new(false)
|
500
|
+
None = TestML::None.new
|
501
|
+
end
|