formkl
Version:
Form marKup Language
517 lines (516 loc) • 17.3 kB
JavaScript
var d = Object.defineProperty;
var E = (s, e, t) => e in s ? d(s, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : s[e] = t;
var c = (s, e, t) => (E(s, typeof e != "symbol" ? e + "" : e, t), t);
const n = (s) => new RegExp(
`^\\b(${[
s,
s.toLowerCase(),
s.toUpperCase(),
s.toLowerCase().charAt(0).toUpperCase() + s.toLowerCase().slice(1)
].join("|")})\\b`
), y = [
[/^\n/, null],
[/^\s+/, null],
[/^\/\/.*/, null],
[/^\/\*[\s\S]*?\*\//, null],
[/^;/, ";"],
[/^{/, "{"],
[/^}/, "}"],
[/^\(/, "("],
[/^\)/, ")"],
[/^\[/, "["],
[/^\]/, "]"],
[/^,/, ","],
[/^\./, "."],
[/^[<>]=?/, "OPERATOR_RELATIONAL"],
[/^[=!]=/, "OPERATOR_EQUALITY"],
[/^&&/, "LOGICAL_AND"],
[/^\|\|/, "LOGICAL_OR"],
[/^!/, "LOGICAL_NOT"],
[n("VALID"), "VALID"],
[n("REGEX"), "REGEX"],
[n("URL"), "URL"],
[n("REQUIRE"), "REQUIRE"],
[n("AS"), "AS"],
[n("OR"), "OR"],
[n("AND"), "AND"],
[n("HAS"), "HAS"],
[n("INCLUDES"), "HAS"],
[n("FORMKL"), "FORMKL"],
[n("MULTIPLE"), "MULTIPLE"],
[n("BASE"), "BASE"],
[n("FLAT"), "FLAT"],
[n("GET"), "HTTPMETHOD"],
[n("POST"), "HTTPMETHOD"],
[n("PUT"), "HTTPMETHOD"],
[n("PATCH"), "HTTPMETHOD"],
[n("DELETE"), "HTTPMETHOD"],
...["text", "paragraph", "number", "switch"].map(
(s) => [n(s), "FIELD"]
),
...["checkbox", "radio", "select"].map(
(s) => [n(s), "FIELDSELECTION"]
),
...["email", "zip", "age"].map((s) => [n(s), "FIELDVALIDATED"]),
...["datetimerange", "datetime", "daterange", "timerange", "time", "date"].map(
(s) => [n(s), "FIELDDATETIME"]
),
[/^\$\w+/, "FIELDCUSTOM"],
[/^\d+/, "NUMBER"],
[/^"[^"]*"/, "STRING"],
[/^'[^']*'/, "STRING"],
[n("NaN"), "NAN"],
[n("FALSE"), "FALSE"],
[n("TRUE"), "TRUE"],
[n("NULL"), "NULL"],
[n("UNDEFINED"), "UNDEFINED"]
];
class L {
constructor(e) {
c(this, "syntax");
c(this, "cursor");
c(this, "currentLine");
c(this, "currentColumn");
this.syntax = e, this.cursor = 0, this.currentLine = 1, this.currentColumn = 0;
}
isEOF() {
return this.cursor === this.syntax.length;
}
hasMoreTokens() {
return this.cursor < this.syntax.length;
}
getNextToken() {
if (!this.hasMoreTokens())
return null;
const e = this.syntax.slice(this.cursor);
for (const [t, i] of y) {
const a = this._match(t, e);
if (a !== null)
return i === null ? this.getNextToken() : {
type: i,
value: a
};
}
throw new SyntaxError(
`Unexpected token: "${e[0]}" at ${this.currentLine}:${this.currentColumn}`
);
}
_match(e, t) {
const i = e.exec(t);
return i === null ? null : (this.cursor += i[0].length, e.source === "^\\n" && i !== null && (this.currentLine++, this.currentColumn = 0), this.currentColumn += i[0].length, i[0]);
}
}
const u = (s) => s.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/\s+/g, "-").toLowerCase();
class g {
constructor() {
}
validateLogicAnd(e) {
return e.map((i) => this.validationLogic(i)).join("%(s)and%(s)");
}
validateLogicOr(e) {
return e.map((i) => this.validationLogic(i)).join("%(s)or%(s)");
}
validationLogic(e) {
const i = Object.keys(e)[0], a = e[i];
return {
$gt: () => `>%(s)${a}`,
$lt: () => `>=%(s)${a}`,
$gteq: () => `<%(s)${a}`,
$lteq: () => `<=%(s)${a}`,
$eq: () => `==%(s)${typeof a == "string" ? JSON.stringify(a) : a}`,
$has: () => `has%(s)${typeof a == "string" ? JSON.stringify(a) : a}`,
$and: () => e.$and && this.validateLogicAnd(e.$and),
$or: () => e.$or && this.validateLogicOr(e.$or)
}[i]();
}
validation(e) {
return [
e.regex && `regex("${e.regex.source}")`,
e.logic && `valid(${this.validationLogic(e.logic)})`
].filter((t) => t).join("%(s)");
}
selectionField(e) {
return `${e.type}${e.fetchUrl ? `${e.fetchDataPath ? `%(s)${e.fetchDataPath}` : ""}%(s)url("${[
e.fetchUrl,
e.valueKey,
e.labelKey
].map((t) => JSON.stringify(t)).join(", ")}")` : `%(s)(${e.options.map((t) => JSON.stringify(t)).join(", ")})`}`;
}
fields(e) {
return "%(t)%(t)" + e.map(
(t) => [
t.required && "require",
t.maxResponseAllowed ? t.maxResponseAllowed : t.multiple && "multiple",
u(t.label).toLowerCase() !== t.type && `"${t.label}"`,
["select", "radio", "checkbox"].includes(t.type) ? this.selectionField(t) : t.type,
t.validation && this.validation(t.validation),
u(t.label).toLowerCase() !== t.key && `as%(s)"${t.key}"`
].filter((i) => i).join("%(s)") + ";"
).join("%(n)%(t)%(t)");
}
sections(e) {
return e.map(
(t) => [
"%(t)",
t.multiple && "multiple%(s)",
t.title && `"${t.title}"%(s)`,
"has",
"%(s)",
"{",
"%(n)",
this.fields(t.fields),
"%(n)",
"%(t)",
"}",
!t.title && t.key || t.title && u(t.title).toLowerCase() !== t.key ? `%(s)as%(s)"${t.key}"` : ""
].join("")
).join("%(n)");
}
stringify(e) {
return `${[
"formkl",
e.model === "flat" && "flat",
e.title && JSON.stringify(e.title),
e.description && JSON.stringify(e.description)
].filter((t) => t).join("%(s)")}%(s){%(n)${this.sections(e.sections)}%(n)}`.replace(/\%\(s\)/g, " ").replace(/\%\(t\)/g, " ").replace(/\%\(n\)/g, `
`);
}
}
const _ = (s) => String(s).toLowerCase().charAt(0).toUpperCase() + String(s).toLowerCase().slice(1);
class S {
constructor() {
c(this, "syntax");
c(this, "tokenizer");
c(this, "_lookahead");
this.syntax = "", this.tokenizer = new L(""), this._lookahead = null;
}
parse(e) {
return this.syntax = "", this._lookahead = null, this.syntax = e, this.tokenizer = new L(this.syntax), this._lookahead = this.tokenizer.getNextToken(), this.FormBlock();
}
stringify(e) {
return new g().stringify(e);
}
FormBlock() {
var i, a, o, l, r;
const e = {
model: "base",
sections: []
};
this._eat("FORMKL"), ((i = this._lookahead) == null ? void 0 : i.type) === "FLAT" ? (this._eat("FLAT"), e.model = "flat") : (((a = this._lookahead) == null ? void 0 : a.type) === "BASE" && this._eat("BASE"), e.model = "base"), ((o = this._lookahead) == null ? void 0 : o.type) === "HTTPMETHOD" && (e.method = this._eat("HTTPMETHOD").value, this._eat("("), e.endpoint = this.StringLiteral(), this._eat(")")), ((l = this._lookahead) == null ? void 0 : l.type) === "STRING" && (e.title = this.StringLiteral()), ((r = this._lookahead) == null ? void 0 : r.type) === "STRING" && (e.description = this.StringLiteral()), this._eat("{");
const t = this.SectionBlockList();
if (t.length > 1) {
const h = /* @__PURE__ */ new Set();
t.forEach((p) => {
if (h.has(p.key))
throw new SyntaxError(
`Duplicate section key "${p.key}", this will make the your schema looks confusing! Please use different aliases if your sections have the same title.`
);
h.add(p.key);
});
}
return Object.assign(e, { sections: t }), this._eat("}"), e;
}
SectionBlockList(e = "}") {
var i;
const t = [this.SectionBlock()];
for (; this._lookahead != null && ((i = this._lookahead) == null ? void 0 : i.type) !== e; )
t.push(this.SectionBlock());
return t;
}
SectionBlock() {
var i, a, o, l;
const e = {
fields: []
};
((i = this._lookahead) == null ? void 0 : i.type) === "NUMBER" ? (e.maxResponseAllowed = this.NumericLiteral(), e.multiple = !0) : ((a = this._lookahead) == null ? void 0 : a.type) === "MULTIPLE" && (this._eat("MULTIPLE"), e.multiple = !0), ((o = this._lookahead) == null ? void 0 : o.type) === "STRING" && (e.title = this.StringLiteral(), e.key = u(e.title).toLowerCase()), this._eat("HAS"), this._eat("{");
const t = this.FieldStatementList();
if (this._eat("}"), ((l = this._lookahead) == null ? void 0 : l.type) === "AS" && (this._eat("AS"), e.key = this.StringLiteral()), t.length > 1) {
const r = /* @__PURE__ */ new Set();
t.forEach((h) => {
if (r.has(h.key))
throw new SyntaxError(
`Duplicate field key "${h.key}", this will make the your schema looks confusing! Please use different aliases if your fields have the same name.`
);
r.add(h.key);
});
}
if (e.multiple && t.some((r) => r.multiple))
throw new SyntaxError(
"A section with multiple responses cannot have fields that also have multiple responses!"
);
return Object.assign(e, { fields: t }), e;
}
FieldStatementList(e = "}") {
var i;
const t = [this.FieldStatement()];
for (; this._lookahead !== null && ((i = this._lookahead) == null ? void 0 : i.type) !== e; )
t.push(this.FieldStatement());
return t;
}
FieldStatement() {
var t, i, a, o, l, r;
const e = {
type: "text",
label: "",
key: ""
};
return ((t = this._lookahead) == null ? void 0 : t.type) === "NUMBER" && (e.maxResponseAllowed = this.NumericLiteral(), e.multiple = !0), ((i = this._lookahead) == null ? void 0 : i.type) === "REQUIRE" && (this._eat("REQUIRE"), e.required = !0), ((a = this._lookahead) == null ? void 0 : a.type) === "MULTIPLE" && (this._eat("MULTIPLE"), e.multiple = !0), ((o = this._lookahead) == null ? void 0 : o.type) === "STRING" ? e.label = this.StringLiteral() : e.label = _(String((l = this._lookahead) == null ? void 0 : l.value).replace(/^\$/g, "")), e.key = u(e.label).toLowerCase(), Object.assign(e, this.FieldExpression()), ((r = this._lookahead) == null ? void 0 : r.type) === "AS" && (this._eat("AS"), e.key = this.StringLiteral()), this._eat(";"), e;
}
FieldExpression() {
var i, a;
const e = {}, t = {
FIELD: this.FieldDefaultExpression.bind(this),
FIELDCUSTOM: this.FieldCustomExpression.bind(this),
FIELDSELECTION: this.FieldSelectionExpression.bind(this),
FIELDVALIDATED: this.FieldValidatedExpression.bind(this),
FIELDDATETIME: this.FieldDatetimeExpression.bind(this)
}[String((i = this._lookahead) == null ? void 0 : i.type)];
if (t) {
Object.assign(e, t());
const o = this.ValidationExpression();
Object.assign(e, o);
} else
throw new SyntaxError(`Unsupported field type "${(a = this._lookahead) == null ? void 0 : a.value}"`);
return e;
}
FieldDefaultExpression() {
var t;
const e = {
type: "text"
};
if (((t = this._lookahead) == null ? void 0 : t.type) === "FIELD") {
const i = this._eat("FIELD").value;
Object.assign(e, {
type: i.toLowerCase()
});
}
return e;
}
FieldCustomExpression() {
var t;
const e = {
type: "text"
};
if (((t = this._lookahead) == null ? void 0 : t.type) === "FIELDCUSTOM") {
const i = this._eat("FIELDCUSTOM").value;
Object.assign(e, {
type: i.toLowerCase()
});
}
return e;
}
FieldSelectionExpression() {
var i, a, o, l;
let e = "";
const t = {
type: "select",
options: []
};
if (((i = this._lookahead) == null ? void 0 : i.type) === "FIELDSELECTION") {
const r = this._eat("FIELDSELECTION").value;
Object.assign(t, {
type: r.toLowerCase()
});
}
if (((a = this._lookahead) == null ? void 0 : a.type) === "STRING" && (e = this.StringLiteral()), ((o = this._lookahead) == null ? void 0 : o.type) === "URL") {
this._eat("URL"), this._eat("(");
const r = this.StringList();
if (r.length > 3)
throw new SyntaxError(
'Selection field fetching data from URL can only have less or equal to 3 arguments ("fetchUrl", "valueKey", "labelKey")'
);
this._eat(")"), Object.assign(t, {
options: [],
fetchUrl: r[0] || "",
valueKey: r[1] || "id",
labelKey: r[2] || "name"
});
}
if (((l = this._lookahead) == null ? void 0 : l.type) === "(") {
this._eat("(");
const r = this.StringList();
this._eat(")"), t.options = r;
}
return Object.assign(t, { fetchDataPath: e }), t;
}
FieldValidatedExpression() {
var t;
const e = {
type: "text"
};
if (((t = this._lookahead) == null ? void 0 : t.type) === "FIELDVALIDATED") {
const i = this._eat("FIELDVALIDATED").value;
Object.assign(e, {
type: i.toLowerCase()
});
}
return e;
}
FieldDatetimeExpression() {
var t;
const e = {
type: "datetime"
};
if (((t = this._lookahead) == null ? void 0 : t.type) === "FIELDDATETIME") {
const i = this._eat("FIELDDATETIME").value;
Object.assign(e, {
type: i.toLowerCase()
});
}
return e;
}
ValidationExpression() {
var t, i, a;
const e = {};
do {
if (((t = this._lookahead) == null ? void 0 : t.type) === "VALID") {
this._eat("VALID"), this._eat("(");
const o = this.LogicalORExpression();
this._eat(")"), Object.assign(e, {
logic: o
});
}
if (((i = this._lookahead) == null ? void 0 : i.type) === "REGEX") {
this._eat("REGEX"), this._eat("(");
const o = this.StringLiteral();
this._eat(")"), Object.assign(e, { regex: new RegExp(o) });
}
} while (["REGEX", "VALID"].includes(String((a = this._lookahead) == null ? void 0 : a.type)));
if (e.logic || e.regex)
return {
validation: e
};
}
LogicalORExpression() {
var t;
const e = [];
do
e.push(this.LogicalANDExpression());
while (((t = this._lookahead) == null ? void 0 : t.type) === "OR" && this._eat("OR"));
return e.length > 1 ? {
$or: e
} : e[0];
}
LogicalANDExpression() {
var t;
const e = [];
do
e.push(this.RelationalExpression());
while (((t = this._lookahead) == null ? void 0 : t.type) === "AND" && this._eat("AND"));
return e.length > 1 ? {
$and: e
} : e[0];
}
RelationalExpression() {
var e, t, i, a, o;
switch ((e = this._lookahead) == null ? void 0 : e.type) {
case "OPERATOR_RELATIONAL":
const l = this._eat("OPERATOR_RELATIONAL").value;
switch (l) {
case ">":
return { $gt: this.NumericLiteral() };
case ">=":
return { $gte: this.NumericLiteral() };
case "<":
return { $lt: this.NumericLiteral() };
case "<=":
return { $lte: this.NumericLiteral() };
default:
throw new SyntaxError(`Unknown relational operator: ${l}`);
}
case "OPERATOR_EQUALITY":
const r = this._eat("OPERATOR_EQUALITY").value, h = r === "==" ? "$eq" : "$neq";
switch (r) {
case "==":
case "!=":
switch ((t = this._lookahead) == null ? void 0 : t.type) {
case "NUMBER":
return { [h]: this.NumericLiteral() };
case "NAN":
return { [h]: this.NaNLiteral() };
case "NULL":
return { [h]: this.NullLiteral() };
case "UNDEFINED":
return { [h]: this.UndefinedLiteral() };
case "TRUE":
case "FALSE":
return {
[h]: this.BooleanLiteral(
(i = this._lookahead) == null ? void 0 : i.type
)
};
case "STRING":
return { [h]: this.StringLiteral() };
default:
throw new SyntaxError(`Unknown equality value type: ${(a = this._lookahead) == null ? void 0 : a.type}`);
}
default:
throw new SyntaxError(`Unknown equality operator: ${r}`);
}
case "HAS":
return this._eat("HAS"), {
$has: isNaN((o = this._lookahead) == null ? void 0 : o.value) ? this.StringLiteral() : this.NumericLiteral()
};
}
}
StringList() {
var t;
const e = [];
do
e.push(this.StringLiteral());
while (((t = this._lookahead) == null ? void 0 : t.type) === "," && this._eat(","));
return e;
}
NaNLiteral() {
return this._eat("NAN"), NaN;
}
NumericLiteral() {
const e = this._eat("NUMBER");
return Number(e.value);
}
StringLiteral() {
const e = this._eat("STRING");
return String(e.value).slice(1, -1);
}
BooleanLiteral(e) {
return this._eat(e ? "TRUE" : "FALSE"), e;
}
NullLiteral() {
return this._eat("NULL"), null;
}
UndefinedLiteral() {
this._eat("UNDEFINED");
}
_eat(e) {
const t = this._lookahead;
if (t === null)
throw new SyntaxError(`Unexpected end of input, expected: "${e}"`);
if (t.type !== e)
throw new SyntaxError(
`Unexpected token: "${t.value}" at ${this.tokenizer.currentLine}:${this.tokenizer.currentColumn}, expected: "${e}"`
);
return this._lookahead = this.tokenizer.getNextToken(), t;
}
}
function T(s) {
return s;
}
function f(s) {
return s;
}
function x(s) {
return s;
}
function A(s) {
return s;
}
const N = new S();
export {
S as Parser,
L as Tokenizer,
N as default,
x as defineField,
T as defineForm,
A as defineModel,
f as defineSection
};