Skip to content

Instantly share code, notes, and snippets.

@skejeton
Created April 13, 2020 19:26
Show Gist options
  • Save skejeton/6f190b61ccba52fa9b294a8e2b5d6938 to your computer and use it in GitHub Desktop.
Save skejeton/6f190b61ccba52fa9b294a8e2b5d6938 to your computer and use it in GitHub Desktop.
const PathMaster = require("./pathparser.js");
console.log(PathMaster.PathParser.parse("///{sfsmx}/{=test_any}/{/[a-z]/g}_{/[a-z]/g}/{&functionName}/"));
/*
Output :
[
PathParameter { type: 4, value: 'sfsmx' },
PathParameter { type: 2, value: 'test_any' },
PathParameter { type: 1, value: /[a-z]/g },
PathParameter { type: 0, value: '_' },
PathParameter { type: 1, value: /[a-z]/g },
PathParameter { type: 3, value: 'functionName' }
]
*/
class PathParameter
{
constructor(type, value)
{
this.type = type;
this.value = value;
}
}
PathParameter.Type = {
Match: 0,
Regex: 1,
Numeric: 2,
Function: 3,
Whatever: 4
}
class ParserResult
{
constructor()
{
this.path = [];
}
add(param)
{
this.path.push(param);
}
}
class PathParser
{
// Method for parameter type PathParameter.Type.Function
setFunctionMethod(fn)
{
this.functionMethod = fn;
}
// Get enum string name by value
static stringEnum(enumeration, value)
{
for (var key in enumeration)
{
if (enumeration[key] == value) return key;
}
return null;
}
// Check type, when we receive parameter we need to check if whatever inside the scope is actually valid.
// So when we receive string like {{id}, the numeric parameter type was implied but it can't contain '{' character in name.
static checkType(context)
{
let allowed = /([a-zA-Z_$][a-zA-Z0-9$_]*)/g
if (context.state == PathParser.State.Path)
{
if (context.template[ptr].search(allowed) > 0)
{
if (context.type != PathParameter.Type.Regex)
{
throw new Error(`[ PathParser ] not allowed character in template \`${context.template}\` at ${context.ptr} in ${context.template[context.ptr]}.
Assuming parameter type ${PathParser.stringEnum(PathParameter.Type, context.type)}
`);
}
}
}
}
static matchEnd(context)
{
if (context.type == PathParameter.Type.Regex)
{
// Will match 2 characters
let endMatch = /[^\\]\//;
let sr = context.template.slice(context.ptr).search(endMatch);
if (sr !== -1)
{
context.ptr += sr + 2;
}
else
{
throw new Error("[ PathParser ] Unmatched end of regular expression parameter, use following pattern {/your_regex/flags}")
}
}
let matchEnd = context.template.slice(context.ptr).search(`}`);
if (matchEnd === -1)
{
throw new Error(`Unmatched end of parameter of type ${PathParser.stringEnum(PathParser.Type, context.type)}
use following pattern {${PathParser.stringEnum(PathParser.ParamType, context.type)}your_param} in template \`${context.template}\` at ${context.ptr} in ${context.template[context.ptr]}`);
}
context.ptr += matchEnd+1;
context.currentParamValue = context.template.slice(context.currentParamStart, context.ptr-1);
}
static parseParameter(context)
{
PathParser.checkType(context);
if (context.type == PathParameter.Type.Regex)
{
let v = context.currentParamValue.split("/");
return new PathParameter(context.type, RegExp(v[0], v[1]));
}
else
{
return new PathParameter(context.type, context.currentParamValue);
}
}
static parse(template)
{
let context = {
template: template,
state: PathParser.State.Path,
hadSlash: false,
ptr: 0,
type: PathParameter.Type.Match,
currentParamStart: -1,
currentParamValue: ""
}
let res = [];
while (context.ptr < template.length)
{
if (template[context.ptr] !== "/")
{
context.hadSlash = false;
}
if (template[context.ptr] == "/" && context.state == PathParser.State.Path)
{
if (context.hadSlash)
{
context.ptr += 1;
}
else
{
context.hadSlash = true;
context.ptr += 1;
}
}
else if (template[context.ptr] === "{")
{
if (context.currentParamValue !== '')
res.push(new PathParameter(PathParameter.Type.Match, context.currentParamValue));
if (context.state == PathParser.State.Path)
{
context.state = PathParser.State.ParameterStart;
}
context.ptr += 1;
}
else if (context.state === PathParser.State.ParameterStart)
{
context.type = PathParameter.Type.Whatever;
if (PathParser.ParamType[context.template[context.ptr]])
{
context.type = PathParser.ParamType[context.template[context.ptr]];
context.ptr += 1;
}
context.state = PathParser.State.Parameter;
context.currentParamStart = context.ptr;
}
else if (context.state === PathParser.State.Parameter)
{
PathParser.matchEnd(context);
res.push(PathParser.parseParameter(context));
context.state = PathParser.State.Path;
context.currentParamValue = '';
context.currentParamStart = context.ptr;
}
else if (template[context.ptr] === "/")
{
if (context.currentParamValue !== '')
res.push(new PathParameter(PathParameter.Type.Match, context.currentParamValue));
context.currentParamStart = context.ptr + 1;
context.ptr += 1;
}
else
{
context.currentParamValue += template[context.ptr];
context.ptr += 1;
}
}
return res;
}
}
PathParser.State = {
Path: 0,
ParameterStart: 1,
Parameter: 2,
ParameterEnd: 3
}
PathParser.ParamType = {
"/": PathParameter.Type.Regex,
"=": PathParameter.Type.Numeric,
"&": PathParameter.Type.Function
}
module.exports.ParserResult = ParserResult;
module.exports.PathParameter = PathParameter;
module.exports.PathParser = PathParser;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment