Created
November 7, 2023 19:26
-
-
Save hamza-cskn/1ea7d8b39d748beed67e7114b34b1985 to your computer and use it in GitHub Desktop.
Finite State Machine Delimiter Validation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const State = { | |
next: undefined, | |
isLookingFor(char) { | |
throw new Error('Not implemented'); | |
} | |
}; | |
const assert = (ensureTrue, message="Validation failed.") => { | |
if (!ensureTrue) | |
throw new Error(message); | |
} | |
function getClosing(char) { | |
switch (char) { | |
case '"': return '"'; | |
case '\'': return '\''; | |
case '[': return ']'; | |
case '{': return '}'; | |
case '(': return ')'; | |
default: return undefined; | |
} | |
} | |
function isInString(state) { | |
return state && (state.isLookingFor('"') || state.isLookingFor('\'')); | |
} | |
function createState(oldStates, char) { | |
return { | |
next: oldStates, | |
isLookingFor: (c) => c === char | |
}; | |
} | |
/** | |
* | |
* @param {string} string to be searched. | |
* @param {number} startIndex character index to start search. | |
* | |
* Maps delimiter characters of a string. | |
* | |
* Examples (startIndex = 0): | |
* abc{'xyz'}klm => { isValid: true, startIndex: 3, lastIndex: 9, map: "{''}" } | |
* }}}}}{}{}{}{}{} => { isValid: true, startIndex: 4, lastIndex: 5, map: '{}' } | |
* {{{{{{{{}}}}}}}} => { isValid: true, startIndex: 0, lastIndex: 15, map: '{{{{{{{{}}}}}}}}' } | |
* {'{'} => { isValid: true, startIndex: 0, lastIndex: 4, map: "{''}" } | |
* { => { isValid: false } | |
* {['']} => { isValid: true, startIndex: 0, lastIndex: 5, map: "{['']}" } | |
* {'}' => { isValid: false } | |
* | |
* @returns {isValid: boolean, startIndex: number, lastIndex: number, map: string} | |
*/ | |
function mapString(string, startIndex = 0) { | |
assert(string, "String is not defined."); | |
assert(typeof string == "string", "First argument must be string."); | |
assert(startIndex >= 0, "startIndex must be non-negative."); | |
if (startIndex >= string.length) | |
return {isValid: false, lastIndex: startIndex, map: ""}; | |
let state = undefined; | |
let map = []; | |
let firstOpening = undefined; | |
let i; | |
for (i = startIndex; i < string.length; i++) { | |
const char = string[i]; | |
let closing = getClosing(char); | |
if (char == '\\') { | |
i++; | |
continue; | |
} | |
if (state && state.isLookingFor(char)) { | |
state = state.next; | |
map.push(char); | |
if (state == undefined) | |
break; | |
continue; // to prevent new state to be created. if closing and opening are same. | |
} | |
if (closing !== undefined && !isInString(state)) { | |
if (firstOpening === undefined) | |
firstOpening = i; | |
state = createState(state, closing); | |
map.push(char); | |
} | |
} | |
if (state) | |
return {isValid: false}; | |
return {isValid: true, startIndex: firstOpening, lastIndex: i, map: map.join("")}; | |
} | |
/** | |
* | |
* @param {string} format to be matched. | |
* @param {string} string to be searched. | |
* | |
* Maps format and searches maps of string. | |
* Maps are created by <code>mapString</code> function. | |
* | |
* If a map of string is not equal to map of format, | |
* it will be skipped until a match found or end of string. | |
* | |
* Example: | |
* Format = "{''}" | |
* String = "superRandomizedString{'lorem ipsum'}superRandomizedString" | |
* Result = {isValid: true, stringResult: {isValid: true, startIndex: 21, lastIndex: 35, map: "{''}"} | |
* | |
* @returns {isValid: boolean, startIndex: number, length: number} | |
*/ | |
function matchMapping(format, string) { | |
assert(format, "Format is not defined."); | |
assert(string, "String is not defined."); | |
assert(typeof format == "string", "First argument must be string."); | |
assert(typeof string == "string", "Second argument must be string."); | |
const formatResult = mapString(format); | |
assert(formatResult.isValid, "Format is not valid."); | |
let stringResult = undefined; | |
let lastIndex = 0; | |
while (true) { | |
stringResult = mapString(string, lastIndex); | |
if (!stringResult.isValid) | |
return {isValid: false, stringResult}; | |
if (stringResult.map == formatResult.map) | |
return {isValid: true, stringResult}; | |
lastIndex = stringResult.lastIndex + 1; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment