Skip to content

Instantly share code, notes, and snippets.

@hamza-cskn
Created November 7, 2023 19:26
Show Gist options
  • Save hamza-cskn/1ea7d8b39d748beed67e7114b34b1985 to your computer and use it in GitHub Desktop.
Save hamza-cskn/1ea7d8b39d748beed67e7114b34b1985 to your computer and use it in GitHub Desktop.
Finite State Machine Delimiter Validation
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