Overview
A simple coding exercise, parsing parentheses expressions
- Given a string containing the characters '(', ')', '{', '}', '[' and ']' and any other characters in between determine if the parentheses are balanced.
Valid Expressions
- "()"- "(){}[]"
- "([ok])"
- "{([ok])}"
- "function() { return (1+1); }"
Invalid Expressions
- "(]"- "([)]"
- "([ok]})"
- "{([ok])})"
Github Repo: ParenthesesParser
Source Code
let Stack = require('./Stack'); let print = console.log; function ParenthesesParser(expression) { this._parentheseDefinition = { '(' : ')', '{' : '}', '[' : ']', }; this.parse = function(expression) { if(!expression) throw new Error(ParenthesesParser.EXPRESSION_CANNOT_BE_NULL_OR_UNDEFINED); let stack = new Stack(); for(let i = 0; i < expression.length; i++) { let c = expression.charAt(i); if(this.isOpenParenthese(c)) { stack.push(c); } else { if(this.isCloseParenthese(c)) { let p = stack.pop(); if(c !== this._parentheseDefinition[p]) // Fail return this.createResult(false, `position:${i}, expected:${this._parentheseDefinition[p]}, found:${c}`); } } } // if stack is empty the expression is valid else we have an invalid expression // with a missing closing parenthese like this '(ok' const passed = stack.isEmpty(); return this.createResult(passed, passed ? `` : `expected:"${this._parentheseDefinition[stack.pop()]}" at end of expression`); } this.createResult = function (passed, errorMsg) { return { Expression : this._expression, Passed : passed, ErrorMessage : ParenthesesParser.PARSING_ERROR + " " + errorMsg }; } this.isOpenParenthese = function(c) { return c in this._parentheseDefinition; } this.isCloseParenthese = function(c) { return Object.values(this._parentheseDefinition).indexOf(c) !== -1; } }; ParenthesesParser.EXPRESSION_CANNOT_BE_NULL_OR_UNDEFINED = "expression cannot be null or undefined"; ParenthesesParser.PARSING_ERROR = "Parsing Error"; module.exports = ParenthesesParser;
Unit tests
let assert = require('chai').assert; let expect = require('chai').expect; let ParenthesesParser = require('../src/ParenthesesParser'); let print = console.log; describe('ParenthesesParser', () => { let subject; before(() => { subject = new ParenthesesParser(); }); describe('Valid expressions', () => { let validExpressions = [ "()" ,"(a)", "((((()))))", "((((([[[]]])))))", "((((([[[{{{}}}]]])))))", "((((([[[{{{a}}}]]])))))", "function(abs(compute(run(exec(array[list[array[object{object{object[1]}}]]])))))", "(){}[]" , "(()){{}}[[]]" , "([ok])" , "{([ok])}" , "function() { return (1+1); }" , "<(>)", // This one passed because '<' and '>' are not considered Parentheses "(\\)", "(\\//)", "(\")", "Valid-Expression", "(Valid-Expression)" ]; it(`should return passed on expression '${validExpressions}'`, () => { validExpressions.forEach((exp) =>{ expect(subject.parse(exp).Passed).to.be.true; }); }); }); describe('Invalid expressions', () => { let invalidExpressions = [ "(invalid-expression", "invalid-expression)", "(" , "((((())))", , "(((((])))))", "[", "{", "(){[]" , "([ok)" , "{([ok]}" , "function() { return (1+1; }" , ]; it(`should return failed on expression '${invalidExpressions}'`, () => { invalidExpressions.forEach((exp) =>{ expect(subject.parse(exp).Passed).to.be.false; }); }); it(`should return specific error message on failure`, () => { let exp = "[(a])"; let expected = "Parsing Error position:3, expected:), found:]"; let result = subject.parse(exp); expect(result.ErrorMessage).to.equal(expected); expect(result.ErrorMessage.startsWith(ParenthesesParser.PARSING_ERROR)).to.be.true; }); it(`should return specific error message on failure, complex expression`, () => { let exp = "((((([[[{{{a)}}}]]])))))"; let expected = "Parsing Error position:12, expected:}, found:)"; let result = subject.parse(exp); expect(result.ErrorMessage).to.equal(expected); }); }); describe('Invalid expressions, missing closing parenthese', () => { it(`should return specific error message on failure on '(ok'`, () => { let exp = "(ok"; let expected = 'Parsing Error expected:")" at end of expression'; let result = subject.parse(exp); expect(result.ErrorMessage).to.equal(expected); }); let invalidExpressionsMissingClosingParentheses = [ "(ok", "{ok", "[ok", ]; it(`should return error message on failure ${invalidExpressionsMissingClosingParentheses}`, () => { invalidExpressionsMissingClosingParentheses.forEach((exp) => { let result = subject.parse(exp); expect(result.ErrorMessage.startsWith(ParenthesesParser.PARSING_ERROR)).to.be.true; }); }); }); describe('Invalid parameters', () => { it(`should throw exception on empty string`, () => { expect(function () { subject.parse("");}).to.throw(ParenthesesParser.EXPRESSION_CANNOT_BE_NULL_OR_UNDEFINED); }); it(`should throw exception on null string`, () => { expect(function () { subject.parse(null);}).to.throw(ParenthesesParser.EXPRESSION_CANNOT_BE_NULL_OR_UNDEFINED); }); it(`should throw exception on undefined string`, () => { expect(function () { subject.parse(undefined);}).to.throw(ParenthesesParser.EXPRESSION_CANNOT_BE_NULL_OR_UNDEFINED); }); }); describe('Utility methods', () => { it(`isOpenParenthese() positive cases`, () => { ['(', "{", "["].forEach((c) => { expect(subject.isOpenParenthese(c)).to.be.true; }); }); it(`isOpenParenthese() negative cases`, () => { ['a', "b", "c"].forEach((c) => { expect(subject.isOpenParenthese(c)).to.be.false; }); }); it(`isCloseParenthese() positive cases`, () => { [')', "}", "]"].forEach((c) => { expect(subject.isCloseParenthese(c)).to.be.true; }); }); it(`isCloseParenthese() negative cases`, () => { ['a', "b", "c"].forEach((c) => { expect(subject.isCloseParenthese(c)).to.be.false; }); }); }); });
No comments:
Post a Comment