bloodyroots
Version:
Recursive descent parser
300 lines (242 loc) • 14 kB
text/coffeescript
inspect_orig = require('util').inspect
inspect = (x) -> inspect_orig(x, false, null)
deep_equal = require('deep-equal')
chai = require('chai')
assert = chai.assert
expect = chai.expect
should = chai.should
{ Parser } = require('../lib/bloodyroots')
String.prototype.repeat = (num) ->
new Array( num + 1 ).join( this );
describe 'Parser', ->
describe 'unit testing', ->
describe 'at_least_one', ->
class TestParser extends Parser
p = new TestParser()
describe 'with none', ->
r = p.parse('dabc')
it 'should not parse', -> assert not r?
describe 'with one', ->
r = p.parse('abcdef')
r_correct = `{ pos: 0,
length: 3,
type: 'seq',
seq: [ { pos: 0, length: 3, type: 're', match: 'abc', groups: [ 'abc' ] } ] }`
it 'should parse', -> assert deep_equal(r, r_correct)
describe 'with two', ->
r = p.parse('abcabcdef')
r_correct = `{ pos: 0,
length: 6,
type: 'seq',
seq: [ { pos: 0, length: 3, type: 're', match: 'abc', groups: [ 'abc' ] },
{ pos: 3, length: 3, type: 're', match: 'abc', groups: [ 'abc' ] } ] }`
it 'should parse', -> assert deep_equal(r, r_correct)
describe 'zero_or_more', ->
class TestParser extends Parser
p = new TestParser()
describe 'with none', ->
r = p.parse('dabc')
r_correct = `{ pos: 0,
length: 0,
type: 'seq',
seq: [ ] }`
it 'should parse', -> assert deep_equal(r, r_correct)
describe 'with one', ->
r = p.parse('abcdef')
r_correct = `{ pos: 0,
length: 3,
type: 'seq',
seq: [ { pos: 0, length: 3, type: 're', match: 'abc', groups: [ 'abc' ] } ] }`
it 'should parse', -> assert deep_equal(r, r_correct)
describe 'with two', ->
r = p.parse('abcabcdef')
r_correct = `{ pos: 0,
length: 6,
type: 'seq',
seq: [ { pos: 0, length: 3, type: 're', match: 'abc', groups: [ 'abc' ] },
{ pos: 3, length: 3, type: 're', match: 'abc', groups: [ 'abc' ] } ] }`
it 'should parse', -> assert deep_equal(r, r_correct)
describe 'zero_or_one', ->
class TestParser extends Parser
p = new TestParser()
describe 'with none', ->
r = p.parse('dabc')
r_correct = `{ pos: 0,
length: 0,
type: 'seq',
seq: [ ] }`
it 'should match zero times', -> assert deep_equal(r, r_correct)
describe 'with one', ->
r = p.parse('abcdef')
r_correct = `{ pos: 0,
length: 3,
type: 'seq',
seq: [ { pos: 0, length: 3, type: 're', match: 'abc', groups: [ 'abc' ] } ] }`
it 'should match one time', -> assert deep_equal(r, r_correct)
describe 'with two', ->
r = p.parse('abcabcdef')
r_correct = `{ pos: 0,
length: 3,
type: 'seq',
seq: [ { pos: 0, length: 3, type: 're', match: 'abc', groups: [ 'abc' ] } ] }`
it 'should match one time', -> assert deep_equal(r, r_correct)
describe 'alternation', ->
describe 'parsing abcde with (abc|ab)cde', ->
# suffix
describe 'with suffix', ->
class TestParser extends Parser
p = new TestParser()
r = p.parse('abcde')
it 'should parse', -> assert r? and r.length == 5
describe 'without suffix', ->
class TestParser extends Parser
p = new TestParser()
r = p.parse('abcde')
it 'should not parse', -> assert not r?
describe 'range', ->
describe 'parsing abc{m}abc{k}def with abc{i,j}def', ->
for i in [0..6]
do (i) ->
for j in [0..6].concat(undefined)
do (j) ->
for k in [0..1]
do (k) ->
# non-greedy no-suffix
class TestParser extends Parser
p = new TestParser()
for m in [0..5]
do (m) ->
describe 'with non-greedy no-suffix m=%d k=%d i=%d j=%d'.sprintf(m,k,i,j), ->
r = p.parse('abc'.repeat(m) + 'def')
if i + k == m and (not j? or i <= j)
it 'should succeed', -> assert r?
else
it 'should fail', -> assert not r?
# greedy no-suffix
class TestParser extends Parser
p = new TestParser()
for m in [0..5]
do (m) ->
describe 'with greedy no-suffix m=%d k=%d i=%d j=%d'.sprintf(m,k,i,j), ->
r = p.parse('abc'.repeat(m) + 'def')
if k == 0
if i <= m and (not j? or j >= m)
it 'should succeed', -> assert r?
else
it 'should fail', -> assert not r?
else
if j? and j + k == m and i <= j
it 'should succeed', -> assert r?
else
it 'should fail', -> assert not r?
# non-greedy suffix
class TestParser extends Parser
p = new TestParser()
for m in [0..5]
do (m) ->
describe 'with non-greedy suffix m=%d k=%d i=%d j=%d'.sprintf(m,k,i,j), ->
r = p.parse('abc'.repeat(m) + 'def')
if i + k <= m and (not j? or j + k >= m)
it 'should succeed', -> assert r?
else
it 'should fail', -> assert not r?
# greedy suffix
class TestParser extends Parser
p = new TestParser()
for m in [0..5]
do (m) ->
describe 'with greedy suffix m=%d k=%d i=%d j=%d'.sprintf(m,k,i,j), ->
r = p.parse('abc'.repeat(m) + 'def')
if i + k <= m and (not j? or j + k >= m)
it 'should succeed', -> assert r?
else
it 'should fail', -> assert not r?
# distinguishes between greedy suffix and non-greedy suffix
describe 'parsing ab{k}babab with ([ab]{2}){i,j}bab', ->
for i in [0..6]
do (i) ->
for j in [0..6].concat(undefined)
do (j) ->
# non-greedy suffix
class TestParser extends Parser
p = new TestParser()
for k in [0..5]
do (k) ->
describe 'with non-greedy suffix {%d,%d} on ab{%d}babab'.sprintf(i,j,k), ->
r = p.parse('ab'.repeat(k) + 'babab')
if not j? or (i <= j and j >= k)
if i <= k
it 'should have length %d'.sprintf(3+2*k), -> assert r.length == 3+2*k
else if i == k + 1
it 'should have length %d'.sprintf(5+2*k), -> assert r.length == 5+2*k
else
it 'should fail', -> assert not r?
else
it 'should fail', -> assert not r?
describe 'with BBCode', ->
element = (elt) ->
{
type: 'element',
tag: elt.seq[0].groups[1],
arg: elt.seq[0].groups[2],
contents: elt.seq[1],
}
seq2array = (elt) -> elt.seq
text = (elt) ->
{
type: 'text',
text: elt.match,
}
class BBCodeParser extends Parser
b = new BBCodeParser()
describe 'basic test case', ->
r = b.parse('[zoo][/zoo][/i][q][b=1]text[w][a]text2[/a][/b]')
r_correct = `[ { type: 'element', tag: 'zoo', arg: undefined, contents: [] },
{ type: 'text', text: '[/i]' },
{ type: 'text', text: '[q]' },
{ type: 'element',
tag: 'b',
arg: '1',
contents:
[ { type: 'text', text: 'text' },
{ type: 'text', text: '[w]' },
{ type: 'element',
tag: 'a',
arg: undefined,
contents: [ { type: 'text', text: 'text2' } ] } ] } ]`
it 'should output DOM matching model', ->
assert deep_equal(r, r_correct)
describe 'newline', ->
r = b.parse('[zoo]abc\ndef[/zoo]')
r_correct = `[ { type: 'element', tag: 'zoo', arg: undefined, contents: [ { type: 'text', text: 'abc\ndef' } ] } ]`
it 'should be accepted as text', ->
assert deep_equal(r, r_correct)