summaryrefslogtreecommitdiffstats
path: root/llparse/test/compiler-test.ts
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--llparse/test/compiler-test.ts289
1 files changed, 289 insertions, 0 deletions
diff --git a/llparse/test/compiler-test.ts b/llparse/test/compiler-test.ts
new file mode 100644
index 0000000..39bb69f
--- /dev/null
+++ b/llparse/test/compiler-test.ts
@@ -0,0 +1,289 @@
+import { LLParse } from '../src/api';
+
+import {
+ ALPHA, build, NUM, NUM_SELECT, printMatch, printOff,
+} from './fixtures';
+
+describe('llparse/Compiler', () => {
+ let p: LLParse;
+
+ beforeEach(() => {
+ p = new LLParse();
+ });
+
+ it('should compile simple parser', async () => {
+ const start = p.node('start');
+
+ start.match(' ', start);
+
+ start.match('HTTP', printOff(p, start));
+
+ start.select({
+ CONNECT: 6,
+ DELETE: 4,
+ GET: 1,
+ HEAD: 0,
+ OPTIONS: 5,
+ PATCH: 8,
+ POST: 2,
+ PUT: 3,
+ TRACE: 7,
+ }, printMatch(p, start));
+
+ start.otherwise(p.error(3, 'Invalid word'));
+
+ const binary = await build(p, start, 'simple');
+ await binary.check('GET', 'off=3 match=1\n');
+ });
+
+ it('should optimize shallow select', async () => {
+ const start = p.node('start');
+
+ start.select(NUM_SELECT, printMatch(p, start));
+
+ start.otherwise(p.error(3, 'Invalid word'));
+
+ const binary = await build(p, start, 'shallow');
+ await binary.check('012', 'off=1 match=0\noff=2 match=1\noff=3 match=2\n');
+ });
+
+ it('should support key-value select', async () => {
+ const start = p.node('start');
+
+ start.select('0', 0, printMatch(p, start));
+ start.select('1', 1, printMatch(p, start));
+ start.select('2', 2, printMatch(p, start));
+
+ start.otherwise(p.error(3, 'Invalid word'));
+
+ const binary = await build(p, start, 'kv-select');
+ await binary.check('012', 'off=1 match=0\noff=2 match=1\noff=3 match=2\n');
+ });
+
+ it('should support multi-match', async () => {
+ const start = p.node('start');
+
+ start.match([ ' ', '\t', '\r', '\n' ], start);
+
+ start.select({
+ A: 0,
+ B: 1,
+ }, printMatch(p, start));
+
+ start.otherwise(p.error(3, 'Invalid word'));
+
+ const binary = await build(p, start, 'multi-match');
+ await binary.check(
+ 'A B\t\tA\r\nA',
+ 'off=1 match=0\noff=3 match=1\noff=6 match=0\noff=9 match=0\n');
+ });
+
+ it('should support numeric-match', async () => {
+ const start = p.node('start');
+
+ start.match(32, start);
+
+ start.select({
+ A: 0,
+ B: 1,
+ }, printMatch(p, start));
+
+ start.otherwise(p.error(3, 'Invalid word'));
+
+ const binary = await build(p, start, 'multi-match');
+ await binary.check(
+ 'A B A A',
+ 'off=1 match=0\noff=3 match=1\noff=6 match=0\noff=9 match=0\n');
+ });
+
+ it('should support custom state properties', async () => {
+ const start = p.node('start');
+ const error = p.error(3, 'Invalid word');
+
+ p.property('i8', 'custom');
+
+ const second = p.invoke(p.code.load('custom'), {
+ 0: p.invoke(p.code.match('llparse__print_zero'), { 0: start }, error),
+ 1: p.invoke(p.code.match('llparse__print_one'), { 0: start }, error),
+ }, error);
+
+ start
+ .select({
+ 0: 0,
+ 1: 1,
+ }, p.invoke(p.code.store('custom'), second))
+ .otherwise(error);
+
+ const binary = await build(p, start, 'custom-prop');
+ await binary.check('0110', 'off=1 0\noff=2 1\noff=3 1\noff=4 0\n');
+ });
+
+ it('should return error code/reason', async () => {
+ const start = p.node('start');
+
+ start.match('a', start);
+ start.otherwise(p.error(42, 'some reason'));
+
+ const binary = await build(p, start, 'error');
+ await binary.check('aab', 'off=2 error code=42 reason="some reason"\n');
+ });
+
+ it('should not merge `.match()` with `.peek()`', async () => {
+ const maybeCr = p.node('maybeCr');
+ const lf = p.node('lf');
+
+ maybeCr.peek('\n', lf);
+ maybeCr.match('\r', lf);
+ maybeCr.otherwise(p.error(1, 'error'));
+
+ lf.match('\n', printOff(p, maybeCr));
+ lf.otherwise(p.error(2, 'error'));
+
+ const binary = await build(p, maybeCr, 'no-merge');
+ await binary.check('\r\n\n', 'off=2\noff=3\n');
+ });
+
+ describe('`.match()`', () => {
+ it('should compile to a single-bit table-lookup node', async () => {
+ const start = p.node('start');
+
+ start
+ .match(ALPHA, start)
+ .skipTo(printOff(p, start));
+
+ // TODO(indutny): validate compilation result?
+ const binary = await build(p, start, 'match-bit-check');
+ await binary.check('pecan.is.dead.', 'off=6\noff=9\noff=14\n');
+ });
+
+ it('should compile to a multi-bit table-lookup node', async () => {
+ const start = p.node('start');
+ const another = p.node('another');
+
+ start
+ .match(ALPHA, start)
+ .peek(NUM, another)
+ .skipTo(printOff(p, start));
+
+ another
+ .match(NUM, another)
+ .otherwise(start);
+
+ // TODO(indutny): validate compilation result?
+ const binary = await build(p, start, 'match-multi-bit-check');
+ await binary.check('pecan.135.is.dead.',
+ 'off=6\noff=10\noff=13\noff=18\n');
+ });
+
+ it('should not overflow on signed char in table-lookup node', async () => {
+ const start = p.node('start');
+
+ start
+ .match(ALPHA, start)
+ .match([ 0xc3, 0xbc ], start)
+ .skipTo(printOff(p, start));
+
+ // TODO(indutny): validate compilation result?
+ const binary = await build(p, start, 'match-bit-check');
+ await binary.check('Düsseldorf.', 'off=12\n');
+ });
+
+ it('should match single quotes and forward slashes', async () => {
+ const start = p.node('start');
+
+ start
+ .match('\'', printOff(p, start))
+ .match('\\', printOff(p, start))
+ .otherwise(p.error(3, 'Invalid char'));
+
+ // TODO(indutny): validate compilation result?
+ const binary = await build(p, start, 'escape-char');
+ await binary.check('\\\'', 'off=1\noff=2\n');
+ });
+
+ it('should hit SSE4.2 optimization for table-lookup', async () => {
+ const start = p.node('start');
+
+ start
+ .match(ALPHA, start)
+ .skipTo(printOff(p, start));
+
+ // TODO(indutny): validate compilation result?
+ const binary = await build(p, start, 'match-bit-check-sse');
+ await binary.check('abcdabcdabcdabcdabcdabcdabcd.abcd.',
+ 'off=29\noff=34\n');
+ });
+
+ it('should compile overlapping matches', async () => {
+ const start = p.node('start');
+
+ start.select({
+ aa: 1,
+ aab: 2,
+ }, printMatch(p, start));
+
+ start.otherwise(p.error(3, 'Invalid word'));
+
+ const binary = await build(p, start, 'overlapping-matches');
+ await binary.check('aaaabaa', 'off=2 match=1\noff=5 match=2\n');
+ });
+ });
+
+ describe('`.peek()`', () => {
+ it('should not advance position', async () => {
+ const start = p.node('start');
+ const ab = p.node('ab');
+ const error = p.error(3, 'Invalid word');
+
+ start
+ .peek([ 'a', 'b' ], ab)
+ .otherwise(error);
+
+ ab
+ .match([ 'a', 'b' ], printOff(p, start))
+ .otherwise(error);
+
+ const binary = await build(p, start, 'peek');
+ await binary.check('ab', 'off=1\noff=2\n');
+ });
+ });
+
+ describe('`.otherwise()`', () => {
+ it('should not advance position by default', async () => {
+ const a = p.node('a');
+ const b = p.node('b');
+
+ a
+ .match('A', a)
+ .otherwise(b);
+
+ b
+ .match('B', printOff(p, b))
+ .skipTo(a);
+
+ const binary = await build(p, a, 'otherwise-noadvance');
+ await binary.check('AABAB', 'off=3\noff=5\n');
+ });
+
+ it('should advance when it is `.skipTo()`', async () => {
+ const start = p.node('start');
+
+ start
+ .match(' ', printOff(p, start))
+ .skipTo(start);
+
+ const binary = await build(p, start, 'otherwise-skip');
+ await binary.check('HELLO WORLD', 'off=6\n');
+ });
+
+ it('should skip everything with `.skipTo()`', async () => {
+ const start = p.node('start');
+
+ start
+ .skipTo(start);
+
+ const binary = await build(p, start, 'all-skip');
+ await binary.check('HELLO WORLD', '\n');
+ });
+ });
+});