diff --git a/lib/analyzeDefinition.js b/lib/analyzeDefinition.js index 142bfe3..77711a6 100644 --- a/lib/analyzeDefinition.js +++ b/lib/analyzeDefinition.js @@ -10,6 +10,16 @@ const findRegexpBeginning = require('find-regexp-beginning') * @returns {{ uid: string, generic: boolean, type: string, strategy: string, characters: null|string[], strategy: string, [value]: string, [regex]: RegExp, [valid]: RegExp|null, [indices]: object }} */ function analyzeDefinition (definition, uid) { + // Check if definition is an object + if (!definition || typeof definition !== 'object') { + throw new Error('Missing definition') + } + + // Check if definition has UID + if (uid === void 0) { + throw new Error('Missing UID.') + } + // Check if definition has a type if (!definition.type) { throw new Error('Missing definition type.') diff --git a/lib/buildCharactersCondition.js b/lib/buildCharactersCondition.js index 522ac46..9307373 100644 --- a/lib/buildCharactersCondition.js +++ b/lib/buildCharactersCondition.js @@ -6,7 +6,7 @@ */ function buildCharactersCondition (characters) { // When there is no characters, it's always true - if (characters.length === 0) { + if (!characters || characters.length === 0) { return 'true' } diff --git a/lib/buildNamedRegularExpression.js b/lib/buildNamedRegularExpression.js index 3197e74..2d35a6e 100644 --- a/lib/buildNamedRegularExpression.js +++ b/lib/buildNamedRegularExpression.js @@ -1,65 +1,5 @@ const namedRegexp = require('named-js-regexp') -/** - * Wrapper for regular expression - * with named properties - */ -class NamedRegExp { - /** - * @param {RegExp} regex - * @param {object} indices - */ - constructor (regex, indices) { - this.regex = regex - this.indices = indices - } - - /** - * Execute regular expression - * - * @param {string} str - * @returns {Array|{index:number, input:string}} - */ - exec (str) { - // Execute regular expression - const v = this.regex.exec(str) - - // When not passed, just return null - if (v === null) { - return null - } - - // Build object for groups - v.groups = {} - - // Add any groups are specified - for (let key in this.indices) { - v.groups[key] = v[this.indices[key]] - } - - return v - } - - /** - * Test regular expression against text - * - * @param {string} str - * @returns {boolean} - */ - test (str) { - return this.regex.test(str) - } - - /** - * Parse regular expression to string - * - * @returns {string} - */ - toString () { - return this.regex.toString() - } -} - /** * As `named-js-regexp` library is changing RegExp object, * it's much slower than regular expressions. @@ -85,7 +25,10 @@ function buildNamedRegularExpression (text, flags) { const regex = eval(enhancedRegex.toString()) // Build object which simulates regular expressions - return new NamedRegExp(regex, indices) + return { + regex: regex, + indices: indices + } } module.exports = buildNamedRegularExpression diff --git a/lib/buildTokenDefinitionCode.js b/lib/buildTokenDefinitionCode.js index 4d39c55..74072f2 100644 --- a/lib/buildTokenDefinitionCode.js +++ b/lib/buildTokenDefinitionCode.js @@ -28,7 +28,7 @@ function buildRegexTokenDefinitionCode (definition) { function buildCharacterDefinitionCode (definition) { return `{ type: ${JSON.stringify(definition.type)}, - data: ${buildDataDefinitionCode(definition, null, 'chr')}, + data: ${buildDataDefinitionCode(definition, null, 'code[0]')}, start: index, end: index + 1 }` diff --git a/lib/utils.js b/lib/utils.js index 98af765..8cbda2d 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -26,7 +26,7 @@ function beautify (code) { * Build internal variable name for specified definition * * @param {string} name - * @param {{ uid: string }} definition + * @param {{ uid: string|int }} definition * @returns {string} */ function buildVariableName (name, definition) { diff --git a/tests/e2e/compileLexerSpec.js b/tests/e2e/compileLexerSpec.js new file mode 100644 index 0000000..73f590e --- /dev/null +++ b/tests/e2e/compileLexerSpec.js @@ -0,0 +1,115 @@ +const expect = require('expect.js') +const compile = require('../../lib/compileLexer') + +describe('E2E: Compile lexer', () => { + it('should correctly parse with simple definition', () => { + const tokenize = compile([ + { type: 'WS', value: ' ' } + ]) + + expect(tokenize(' ')).to.eql({ + tokens: [ + { type: 'WS', data: { value: ' ' }, start: 0, end: 1 }, + { type: 'WS', data: { value: ' ' }, start: 1, end: 2 }, + { type: 'WS', data: { value: ' ' }, start: 2, end: 3 } + ] + }) + }) + + it('should correctly parse with regex definition', () => { + const tokenize = compile([ + { type: 'WS', value: ' ' }, + { type: 'LT', regex: '[a-z]+', regexFlags: 'i' }, + ]) + + expect(tokenize(' Sth ')).to.eql({ + tokens: [ + { type: 'WS', data: { value: ' ' }, start: 0, end: 1 }, + { type: 'WS', data: { value: ' ' }, start: 1, end: 2 }, + { type: 'LT', data: { value: 'Sth' }, start: 2, end: 5 }, + { type: 'WS', data: { value: ' ' }, start: 5, end: 6 } + ] + }) + }) + + it('should correctly parse with regex (named groups) definition', () => { + const tokenize = compile([ + { type: 'WS', value: ' ' }, + { type: 'LT', regex: '(?[a-z])(?[a-z]*)', regexFlags: 'i' }, + ]) + + expect(tokenize(' Sth ')).to.eql({ + tokens: [ + { type: 'WS', data: { value: ' ' }, start: 0, end: 1 }, + { type: 'WS', data: { value: ' ' }, start: 1, end: 2 }, + { type: 'LT', data: { first: 'S', later: 'th' }, start: 2, end: 5 }, + { type: 'WS', data: { value: ' ' }, start: 5, end: 6 } + ] + }) + }) + + it('should correctly parse with text definition', () => { + const tokenize = compile([ + { type: 'WS', regex: '[ ]+' }, + { type: 'FN', value: '@fn' }, + ]) + + expect(tokenize(' @fn ')).to.eql({ + tokens: [ + { type: 'WS', data: { value: ' ' }, start: 0, end: 2 }, + { type: 'FN', data: { value: '@fn' }, start: 2, end: 5 }, + { type: 'WS', data: { value: ' ' }, start: 5, end: 6 } + ] + }) + }) + + it('should correctly parse with validated regex definition', () => { + const tokenize = compile([ + { type: 'WS', regex: '[ ]+' }, + { type: 'FN', regex: '@fn', valid: '@fn( |$)' }, + { type: 'WORD', regex: '[^ ]*' }, + ]) + + expect(tokenize(' @fnx ')).to.eql({ + tokens: [ + { type: 'WS', data: { value: ' ' }, start: 0, end: 2 }, + { type: 'WORD', data: { value: '@fnx' }, start: 2, end: 6 }, + { type: 'WS', data: { value: ' ' }, start: 6, end: 7 } + ] + }) + + expect(tokenize(' @fn x')).to.eql({ + tokens: [ + { type: 'WS', data: { value: ' ' }, start: 0, end: 2 }, + { type: 'FN', data: { value: '@fn' }, start: 2, end: 5 }, + { type: 'WS', data: { value: ' ' }, start: 5, end: 6 }, + { type: 'WORD', data: { value: 'x' }, start: 6, end: 7 } + ] + }) + }) + + it('should fail because of validated regex definition', () => { + const tokenize = compile([ + { type: 'WS', regex: '[ ]+' }, + { type: 'FN', regex: '@fn', valid: '@fn( |$)' } + ]) + + expect(tokenize(' @fnx ')).to.eql({ + error: 'Unrecognized token', + index: 2, + line: 1, + column: 3 + }) + }) + + it('should fail because of no definitions', () => { + const tokenize = compile([]) + + expect(tokenize(' @fnx ')).to.eql({ + error: 'Unrecognized token', + index: 0, + line: 1, + column: 1 + }) + }) +}) diff --git a/tests/unit/analyzeDefinitionSpec.js b/tests/unit/analyzeDefinitionSpec.js new file mode 100644 index 0000000..4ae32c2 --- /dev/null +++ b/tests/unit/analyzeDefinitionSpec.js @@ -0,0 +1,202 @@ +const expect = require('expect.js') +const analyze = require('../../lib/analyzeDefinition') + +describe('Analyze definition', () => { + it('should fail without definition', () => { + expect(() => analyze(null, 10)).to.throwError() + }) + + it('should fail without definition type', () => { + expect(() => analyze({ + regex: 'abc' + }, 10)).to.throwError() + }) + + it('should fail without definition value/regex', () => { + expect(() => analyze({ + type: 'xx' + }, 10)).to.throwError() + }) + + it('should fail with both definition value and regex', () => { + expect(() => analyze({ + type: 'xx', + regex: 'abc', + value: 'abc' + }, 10)).to.throwError() + }) + + it('should fail without UID', () => { + expect(() => analyze({ + type: 'xx', + regex: 'abc' + })).to.throwError() + }) + + it('should fail with empty value', () => { + expect(() => analyze({ + type: 'xx', + value: '' + }, 10)).to.throwError() + }) + + it('should build correct single-character definition', () => { + expect(analyze({ + type: 'xx', + value: 'a' + }, 10)).to.eql({ + uid: 10, + generic: false, + type: 'xx', + strategy: 'character', + characters: [ 'a' ], + value: 'a' + }) + }) + + it('should build correct text definition', () => { + expect(analyze({ + type: 'xx', + value: 'abc' + }, 10)).to.eql({ + uid: 10, + generic: false, + type: 'xx', + strategy: 'text', + characters: [ 'a' ], + value: 'abc' + }) + }) + + it('should build correct regex definition', () => { + expect(analyze({ + type: 'xx', + regex: 'abc' + }, 10)).to.eql({ + uid: 10, + generic: false, + characters: [ 'a' ], + type: 'xx', + strategy: 'regex', + regex: /abc/g, + valid: null, + indices: {} + }) + }) + + it('should build correct regex with named groups definition', () => { + expect(analyze({ + type: 'xx', + regex: 'a(?bc)' + }, 10)).to.eql({ + uid: 10, + generic: false, + characters: [ 'a' ], + type: 'xx', + strategy: 'regex', + regex: /a(bc)/g, + valid: null, + indices: { name: 1 } + }) + }) + + it('should build correct validated-regex definition', () => { + expect(analyze({ + type: 'xx', + regex: 'abc', + valid: 'a' + }, 10)).to.eql({ + uid: 10, + generic: false, + characters: [ 'a' ], + type: 'xx', + strategy: 'validatedRegex', + regex: /abc/g, + valid: /a/g, + indices: {} + }) + }) + + it('should build correct validated-regex with named groups definition', () => { + expect(analyze({ + type: 'xx', + regex: 'a(?bc)', + valid: 'a' + }, 10)).to.eql({ + uid: 10, + generic: false, + characters: [ 'a' ], + type: 'xx', + strategy: 'validatedRegex', + regex: /a(bc)/g, + valid: /a/g, + indices: { name: 1 } + }) + }) + + it('should build correct generic regex definition', () => { + expect(analyze({ + type: 'xx', + regex: '.(?bc)' + }, 10)).to.eql({ + uid: 10, + generic: true, + characters: null, + type: 'xx', + strategy: 'regex', + regex: /.(bc)/g, + valid: null, + indices: { name: 1 } + }) + }) + + it('should build correct regex with flags definition', () => { + expect(analyze({ + type: 'xx', + regex: '.(?bc)', + regexFlags: 'i' + }, 10)).to.eql({ + uid: 10, + generic: true, + characters: null, + type: 'xx', + strategy: 'regex', + regex: /.(bc)/gi, + valid: null, + indices: { name: 1 } + }) + + expect(analyze({ + type: 'xx', + regex: 'a(?bc)', + regexFlags: 'i' + }, 10)).to.eql({ + uid: 10, + generic: false, + characters: [ 'a', 'A' ], + type: 'xx', + strategy: 'regex', + regex: /a(bc)/gi, + valid: null, + indices: { name: 1 } + }) + }) + + it('should build correct validated regex with flags definition', () => { + expect(analyze({ + type: 'xx', + regex: '([aA])(?bc)', + valid: 'a', + validFlags: 'i' + }, 10)).to.eql({ + uid: 10, + generic: false, + characters: [ 'a', 'A' ], + type: 'xx', + strategy: 'validatedRegex', + regex: /([aA])(bc)/g, + valid: /a/gi, + indices: { name: 2 } + }) + }) +}) diff --git a/tests/unit/buildCharactersConditionSpec.js b/tests/unit/buildCharactersConditionSpec.js new file mode 100644 index 0000000..94a8e3f --- /dev/null +++ b/tests/unit/buildCharactersConditionSpec.js @@ -0,0 +1,19 @@ +const expect = require('expect.js') +const build = require('../../lib/buildCharactersCondition') + +describe('Build characters condition', () => { + it('should build correct condition without characters', () => { + expect(build()).to.eql('true') + expect(build(null)).to.eql('true') + expect(build([])).to.eql('true') + }) + + it('should build correct condition with single character', () => { + expect(build([ 'a' ])).to.eql('chr === 97') + }) + + it('should build correct condition with multiple characters', () => { + expect(build([ 'a', 'b' ])).to.eql('(chr === 97 || chr === 98)') + expect(build([ 'a', 'b', 'c' ])).to.eql('(chr === 97 || chr === 98 || chr === 99)') + }) +}) diff --git a/tests/unit/utilsSpec.js b/tests/unit/utilsSpec.js index eea6e4a..62f2be9 100644 --- a/tests/unit/utilsSpec.js +++ b/tests/unit/utilsSpec.js @@ -4,5 +4,10 @@ const utils = require('../../lib/utils') describe('Utils', () => { it('should correctly build variable name for definition', () => { expect(utils.variable('v', { uid: '10' })).to.eql('v$10') + expect(utils.variable('v', { uid: 10 })).to.eql('v$10') + }) + + it('should correctly trim spaces', () => { + expect(utils.trimSpaces('abc\n\n \naaa')).to.eql('abc\naaa') }) })