inline-schema/dist/index.mjs

376 lines
10 KiB
JavaScript
Raw Normal View History

// src/parser.ts
var ParseError = class extends Error {
constructor(message, position) {
super(position !== void 0 ? `${message} at position ${position}` : message);
this.position = position;
this.name = "ParseError";
}
};
var Parser = class {
constructor(input) {
this.pos = 0;
this.input = input;
}
peek() {
return this.input[this.pos] || "";
}
consume() {
return this.input[this.pos++] || "";
}
skipWhitespace() {
while (this.pos < this.input.length && /\s/.test(this.input[this.pos])) {
this.pos++;
}
}
match(str) {
return this.input.slice(this.pos, this.pos + str.length) === str;
}
consumeStr(str) {
if (this.match(str)) {
this.pos += str.length;
return true;
}
return false;
}
getPosition() {
return this.pos;
}
getInputLength() {
return this.input.length;
}
parseSchema() {
this.skipWhitespace();
if (this.consumeStr("string")) {
if (this.consumeStr("[")) {
this.skipWhitespace();
if (!this.consumeStr("]")) {
throw new ParseError("Expected ]", this.pos);
}
return { type: "array", element: { type: "string" } };
}
return { type: "string" };
}
if (this.consumeStr("number")) {
if (this.consumeStr("[")) {
this.skipWhitespace();
if (!this.consumeStr("]")) {
throw new ParseError("Expected ]", this.pos);
}
return { type: "array", element: { type: "number" } };
}
return { type: "number" };
}
if (this.consumeStr("boolean")) {
if (this.consumeStr("[")) {
this.skipWhitespace();
if (!this.consumeStr("]")) {
throw new ParseError("Expected ]", this.pos);
}
return { type: "array", element: { type: "boolean" } };
}
return { type: "boolean" };
}
if (this.consumeStr("[")) {
const elements = [];
this.skipWhitespace();
if (this.peek() === "]") {
this.consume();
throw new ParseError("Empty array/tuple not allowed", this.pos);
}
elements.push(this.parseNamedSchema());
this.skipWhitespace();
if (this.consumeStr(";")) {
const remainingElements = [];
while (true) {
this.skipWhitespace();
remainingElements.push(this.parseNamedSchema());
this.skipWhitespace();
if (!this.consumeStr(";")) {
break;
}
}
elements.push(...remainingElements);
}
this.skipWhitespace();
if (!this.consumeStr("]")) {
throw new ParseError("Expected ]", this.pos);
}
if (this.consumeStr("[")) {
this.skipWhitespace();
if (!this.consumeStr("]")) {
throw new ParseError("Expected ]", this.pos);
}
if (elements.length === 1 && !elements[0].name) {
return { type: "array", element: elements[0].schema };
}
return { type: "array", element: { type: "tuple", elements } };
}
if (elements.length === 1 && !elements[0].name) {
return { type: "array", element: elements[0].schema };
}
return { type: "tuple", elements };
}
let identifier = "";
while (this.pos < this.input.length && /[a-zA-Z0-9\-_]/.test(this.peek())) {
identifier += this.consume();
}
if (identifier.length > 0) {
if (this.consumeStr("[")) {
this.skipWhitespace();
if (!this.consumeStr("]")) {
throw new ParseError("Expected ]", this.pos);
}
return { type: "array", element: { type: "string" } };
}
return { type: "string" };
}
throw new ParseError(`Unexpected character: ${this.peek()}`, this.pos);
}
parseNamedSchema() {
this.skipWhitespace();
const startpos = this.pos;
let identifier = "";
while (this.pos < this.input.length && /[a-zA-Z0-9\-_]/.test(this.peek())) {
identifier += this.consume();
}
if (identifier.length === 0) {
throw new ParseError("Expected schema or named schema", this.pos);
}
this.skipWhitespace();
if (this.consumeStr(":")) {
this.skipWhitespace();
const name = identifier;
const schema = this.parseSchema();
return { name, schema };
} else {
this.pos = startpos;
const schema = this.parseSchema();
return { schema };
}
}
};
function parseSchema(schemaString) {
const parser = new Parser(schemaString.trim());
const schema = parser.parseSchema();
if (parser.getPosition() < parser.getInputLength()) {
throw new ParseError("Unexpected input after schema", parser.getPosition());
}
return schema;
}
// src/validator.ts
var ValueParser = class {
constructor(input) {
this.pos = 0;
this.input = input;
}
peek() {
return this.input[this.pos] || "";
}
consume() {
return this.input[this.pos++] || "";
}
skipWhitespace() {
while (this.pos < this.input.length && /\s/.test(this.input[this.pos])) {
this.pos++;
}
}
consumeStr(str) {
if (this.input.slice(this.pos, this.pos + str.length) === str) {
this.pos += str.length;
return true;
}
return false;
}
parseValue(schema, allowOmitBrackets = false) {
this.skipWhitespace();
switch (schema.type) {
case "string":
return this.parseStringValue();
case "number":
return this.parseNumberValue();
case "boolean":
return this.parseBooleanValue();
case "tuple":
return this.parseTupleValue(schema, allowOmitBrackets);
case "array":
return this.parseArrayValue(schema, allowOmitBrackets);
default:
throw new ParseError(`Unknown schema type: ${schema.type}`, this.pos);
}
}
parseStringValue() {
let result = "";
while (this.pos < this.input.length) {
const char = this.peek();
if (char === "\\") {
this.consume();
const nextChar = this.consume();
if (nextChar === ";" || nextChar === "[" || nextChar === "]" || nextChar === "\\") {
result += nextChar;
} else {
result += "\\" + nextChar;
}
} else if (char === ";" || char === "]") {
break;
} else {
result += this.consume();
}
}
return result.trim();
}
parseNumberValue() {
let numStr = "";
while (this.pos < this.input.length && /[\d.\-+eE]/.test(this.peek())) {
numStr += this.consume();
}
const num = parseFloat(numStr);
if (isNaN(num)) {
throw new ParseError("Invalid number", this.pos - numStr.length);
}
return num;
}
parseBooleanValue() {
if (this.consumeStr("true")) {
return true;
}
if (this.consumeStr("false")) {
return false;
}
throw new ParseError("Expected true or false", this.pos);
}
parseTupleValue(schema, allowOmitBrackets) {
let hasOpenBracket = false;
if (this.peek() === "[") {
this.consume();
hasOpenBracket = true;
} else if (!allowOmitBrackets) {
throw new ParseError("Expected [", this.pos);
}
this.skipWhitespace();
if (this.peek() === "]" && hasOpenBracket) {
this.consume();
return [];
}
const result = [];
for (let i = 0; i < schema.elements.length; i++) {
this.skipWhitespace();
const elementSchema = schema.elements[i];
if (elementSchema.name) {
this.skipWhitespace();
if (!this.consumeStr(`${elementSchema.name}:`)) {
throw new ParseError(`Expected ${elementSchema.name}:`, this.pos);
}
this.skipWhitespace();
}
result.push(this.parseValue(elementSchema.schema, false));
this.skipWhitespace();
if (i < schema.elements.length - 1) {
if (!this.consumeStr(";")) {
throw new ParseError("Expected ;", this.pos);
}
}
}
this.skipWhitespace();
if (hasOpenBracket) {
if (!this.consumeStr("]")) {
throw new ParseError("Expected ]", this.pos);
}
}
return result;
}
parseArrayValue(schema, allowOmitBrackets) {
let hasOpenBracket = false;
const elementIsTupleOrArray = schema.element.type === "tuple" || schema.element.type === "array";
if (this.peek() === "[") {
if (!elementIsTupleOrArray) {
this.consume();
hasOpenBracket = true;
} else if (this.input[this.pos + 1] === "[") {
this.consume();
hasOpenBracket = true;
}
}
if (!hasOpenBracket && !allowOmitBrackets && !elementIsTupleOrArray) {
throw new ParseError("Expected [", this.pos);
}
this.skipWhitespace();
if (this.peek() === "]" && hasOpenBracket) {
this.consume();
return [];
}
const result = [];
while (true) {
this.skipWhitespace();
result.push(this.parseValue(schema.element, false));
this.skipWhitespace();
if (!this.consumeStr(";")) {
break;
}
}
this.skipWhitespace();
if (hasOpenBracket) {
if (!this.consumeStr("]")) {
throw new ParseError("Expected ]", this.pos);
}
}
return result;
}
getPosition() {
return this.pos;
}
getInputLength() {
return this.input.length;
}
};
function parseValue(schema, valueString) {
const parser = new ValueParser(valueString.trim());
const allowOmitBrackets = schema.type === "tuple" || schema.type === "array";
const value = parser.parseValue(schema, allowOmitBrackets);
if (parser.getPosition() < parser.getInputLength()) {
throw new ParseError("Unexpected input after value", parser.getPosition());
}
return value;
}
function createValidator(schema) {
return function validate(value) {
switch (schema.type) {
case "string":
return typeof value === "string";
case "number":
return typeof value === "number" && !isNaN(value);
case "boolean":
return typeof value === "boolean";
case "tuple":
if (!Array.isArray(value)) return false;
if (value.length !== schema.elements.length) return false;
return schema.elements.every(
(elementSchema, index) => createValidator(elementSchema.schema)(value[index])
);
case "array":
if (!Array.isArray(value)) return false;
return value.every((item) => createValidator(schema.element)(item));
default:
return false;
}
};
}
// src/index.ts
function defineSchema(schemaString) {
const schema = parseSchema(schemaString);
const validator = createValidator(schema);
return {
schema,
validator,
parse: (valueString) => parseValue(schema, valueString)
};
}
export {
ParseError,
createValidator,
defineSchema,
parseSchema,
parseValue
};