chore: reformat with 2 spaces

This commit is contained in:
Johannes Loher 2023-07-10 22:23:13 +02:00
parent d659e4bed9
commit 7670d7f808
No known key found for this signature in database
GPG key ID: 7CB0A9FB553DA045
1577 changed files with 70010 additions and 70042 deletions

View file

@ -5,32 +5,32 @@
import { Validator } from "./validator";
export class Evaluator<Context extends object> {
context?: Context;
validator: Validator;
context?: Context;
validator: Validator;
constructor({
context,
predicate = Validator.defaultPredicate,
}: { context?: Context; predicate?: (identifier: string) => boolean } = {}) {
let actualPredicate = predicate;
if (context) {
this.context = new Proxy(context, {
has: () => true,
get: (t, k) => (k === Symbol.unscopables ? undefined : t[k as keyof typeof t]),
});
actualPredicate = (identifier: string) =>
predicate(identifier) || Object.getOwnPropertyNames(Math).includes(identifier);
}
this.validator = new Validator(actualPredicate);
constructor({
context,
predicate = Validator.defaultPredicate,
}: { context?: Context; predicate?: (identifier: string) => boolean } = {}) {
let actualPredicate = predicate;
if (context) {
this.context = new Proxy(context, {
has: () => true,
get: (t, k) => (k === Symbol.unscopables ? undefined : t[k as keyof typeof t]),
});
actualPredicate = (identifier: string) =>
predicate(identifier) || Object.getOwnPropertyNames(Math).includes(identifier);
}
this.validator = new Validator(actualPredicate);
}
evaluate(expression: string): unknown {
this.validator.validate(expression);
evaluate(expression: string): unknown {
this.validator.validate(expression);
const body = `with (sandbox) { return ${expression}; }`;
const evaluate = new Function("sandbox", body);
return evaluate(this.context ?? {});
}
const body = `with (sandbox) { return ${expression}; }`;
const evaluate = new Function("sandbox", body);
return evaluate(this.context ?? {});
}
}
export const defaultEvaluator = new Evaluator();

View file

@ -5,57 +5,57 @@
export type Token = TokenWithSymbol | TokenWithoutSymbol;
export interface TokenWithSymbol {
type: TypeWithSymbol;
symbol: string;
pos: number;
type: TypeWithSymbol;
symbol: string;
pos: number;
}
interface TokenWithoutSymbol {
type: TypeWithoutSymbol;
pos: number;
type: TypeWithoutSymbol;
pos: number;
}
type TypeWithSymbol = "iden" | "number" | "string";
type TypeWithoutSymbol =
| "+"
| "-"
| "*"
| "**"
| "/"
| "%"
| "==="
| "!=="
| "=="
| "!="
| "<"
| "<="
| ">"
| ">="
| "&&"
| "||"
| "&"
| "|"
| "~"
| "^"
| "<<"
| ">>"
| ">>>"
| "."
| "?."
| "??"
| "!"
| "?"
| ":"
| "("
| ")"
| "["
| "]"
| ","
| "{"
| "}"
| "invalid"
| "eof";
| "+"
| "-"
| "*"
| "**"
| "/"
| "%"
| "==="
| "!=="
| "=="
| "!="
| "<"
| "<="
| ">"
| ">="
| "&&"
| "||"
| "&"
| "|"
| "~"
| "^"
| "<<"
| ">>"
| ">>>"
| "."
| "?."
| "??"
| "!"
| "?"
| ":"
| "("
| ")"
| "["
| "]"
| ","
| "{"
| "}"
| "invalid"
| "eof";
export const literals = ["true", "false", "null", "undefined"];
export const safeOperators = ["in", "instanceof", "typeof", "void"];

View file

@ -5,260 +5,257 @@
import type { Token } from "./grammar";
export class Lexer {
constructor(private readonly input: string) {}
constructor(private readonly input: string) {}
*[Symbol.iterator](): Generator<Token, void> {
let pos = 0;
while (true) {
if (this.isWhiteSpace(this.input[pos])) {
pos += 1;
continue;
}
const [token, newPos] = this.getNextToken(pos);
pos = newPos;
yield token;
if (token.type === "eof" || token.type === "invalid") {
break;
}
}
*[Symbol.iterator](): Generator<Token, void> {
let pos = 0;
while (true) {
if (this.isWhiteSpace(this.input[pos])) {
pos += 1;
continue;
}
const [token, newPos] = this.getNextToken(pos);
pos = newPos;
yield token;
if (token.type === "eof" || token.type === "invalid") {
break;
}
}
}
private getNextToken(pos: number): [Token, number] {
const current = this.input[pos];
private getNextToken(pos: number): [Token, number] {
const current = this.input[pos];
if (current === undefined) {
return [{ type: "eof", pos }, pos];
if (current === undefined) {
return [{ type: "eof", pos }, pos];
}
if (this.isOperatorStart(current)) {
return this.getOperator(pos);
}
if (this.isDigit(current)) {
return this.getNumber(pos);
}
if (current === "'" || current === '"' || current === "`") {
return this.getString(pos);
}
if (current === ".") {
const next = this.input[pos + 1];
if (this.isDigit(next)) {
return this.getNumber(pos);
}
return this.getOperator(pos);
}
if (this.isIdentifierStart(current)) {
return this.getIdentifier(pos);
}
return [{ type: "invalid", pos }, pos];
}
private isOperatorStart(char: string) {
const operatorStartChars: (string | undefined)[] = [
"+",
"-",
"*",
"/",
"%",
"=",
"!",
">",
"<",
"&",
"|",
"~",
"^",
"?",
":",
"!",
",",
"(",
")",
"[",
"]",
"{",
"}",
];
return operatorStartChars.includes(char[0]);
}
private getOperator(pos: number): [Token, number] {
const current = this.input[pos];
const next = this.input[pos + 1];
const nextButOne = this.input[pos + 2];
switch (current) {
case "+":
case "-":
case "/":
case "%":
case "~":
case "^":
case ".":
case ":":
case ",":
case "(":
case ")":
case "[":
case "]":
case "{":
case "}": {
return [{ type: current, pos }, pos + 1];
}
case "*": {
if (next === "*") {
return [{ type: "**", pos }, pos + 2];
}
if (this.isOperatorStart(current)) {
return this.getOperator(pos);
}
if (this.isDigit(current)) {
return this.getNumber(pos);
}
if (current === "'" || current === '"' || current === "`") {
return this.getString(pos);
}
if (current === ".") {
const next = this.input[pos + 1];
if (this.isDigit(next)) {
return this.getNumber(pos);
}
return this.getOperator(pos);
}
if (this.isIdentifierStart(current)) {
return this.getIdentifier(pos);
return [{ type: "*", pos }, pos + 1];
}
case "=": {
if (next === "=") {
if (nextButOne === "=") {
return [{ type: "===", pos }, pos + 3];
}
return [{ type: "==", pos }, pos + 2];
}
return [{ type: "invalid", pos }, pos];
}
private isOperatorStart(char: string) {
const operatorStartChars: (string | undefined)[] = [
"+",
"-",
"*",
"/",
"%",
"=",
"!",
">",
"<",
"&",
"|",
"~",
"^",
"?",
":",
"!",
",",
"(",
")",
"[",
"]",
"{",
"}",
];
return operatorStartChars.includes(char[0]);
}
private getOperator(pos: number): [Token, number] {
const current = this.input[pos];
const next = this.input[pos + 1];
const nextButOne = this.input[pos + 2];
switch (current) {
case "+":
case "-":
case "/":
case "%":
case "~":
case "^":
case ".":
case ":":
case ",":
case "(":
case ")":
case "[":
case "]":
case "{":
case "}": {
return [{ type: current, pos }, pos + 1];
}
case "*": {
if (next === "*") {
return [{ type: "**", pos }, pos + 2];
}
return [{ type: "*", pos }, pos + 1];
}
case "=": {
if (next === "=") {
if (nextButOne === "=") {
return [{ type: "===", pos }, pos + 3];
}
return [{ type: "==", pos }, pos + 2];
}
return [{ type: "invalid", pos }, pos];
}
case "!": {
if (next === "=") {
if (nextButOne === "=") {
return [{ type: "!==", pos }, pos + 3];
}
return [{ type: "!=", pos }, pos + 2];
}
return [{ type: "!", pos }, pos + 1];
}
case ">": {
switch (next) {
case ">": {
if (nextButOne === ">") {
return [{ type: ">>>", pos }, pos + 3];
}
return [{ type: ">>", pos }, pos + 2];
}
case "=": {
return [{ type: ">=", pos }, pos + 2];
}
default: {
return [{ type: ">", pos }, pos + 1];
}
}
}
case "<": {
switch (next) {
case "=": {
return [{ type: "<=", pos }, pos + 2];
}
case "<": {
return [{ type: "<<", pos }, pos + 2];
}
default: {
return [{ type: "<", pos }, pos + 1];
}
}
}
case "&": {
if (next === "&") {
return [{ type: "&&", pos }, pos + 2];
}
return [{ type: "&", pos }, pos + 1];
}
case "|": {
if (next === "|") {
return [{ type: "||", pos }, pos + 2];
}
return [{ type: "|", pos }, pos + 1];
}
case "?": {
switch (next) {
case ".": {
return [{ type: "?.", pos }, pos + 2];
}
case "?": {
return [{ type: "??", pos }, pos + 2];
}
default: {
return [{ type: "?", pos }, pos + 1];
}
}
}
}
case "!": {
if (next === "=") {
if (nextButOne === "=") {
return [{ type: "!==", pos }, pos + 3];
}
return [{ type: "!=", pos }, pos + 2];
}
return [{ type: "!", pos }, pos + 1];
}
case ">": {
switch (next) {
case ">": {
if (nextButOne === ">") {
return [{ type: ">>>", pos }, pos + 3];
}
return [{ type: ">>", pos }, pos + 2];
}
case "=": {
return [{ type: ">=", pos }, pos + 2];
}
default: {
return [{ type: ">", pos }, pos + 1];
}
}
}
case "<": {
switch (next) {
case "=": {
return [{ type: "<=", pos }, pos + 2];
}
case "<": {
return [{ type: "<<", pos }, pos + 2];
}
default: {
return [{ type: "<", pos }, pos + 1];
}
}
}
case "&": {
if (next === "&") {
return [{ type: "&&", pos }, pos + 2];
}
return [{ type: "&", pos }, pos + 1];
}
case "|": {
if (next === "|") {
return [{ type: "||", pos }, pos + 2];
}
return [{ type: "|", pos }, pos + 1];
}
case "?": {
switch (next) {
case ".": {
return [{ type: "?.", pos }, pos + 2];
}
case "?": {
return [{ type: "??", pos }, pos + 2];
}
default: {
return [{ type: "?", pos }, pos + 1];
}
}
}
}
return [{ type: "invalid", pos }, pos];
}
private isDigit(char: string | undefined): char is `${number}` {
return /\d/.test(char?.[0] ?? "");
}
private getNumber(pos: number): [Token, number] {
let endPos = pos;
let foundDot = false;
let only0s = false;
while (
this.isDigit(this.input[endPos]) ||
this.input[endPos] === "." ||
(this.input[endPos] === "_" && endPos > pos)
) {
if (this.input[endPos] === ".") {
if (foundDot) {
return [{ type: "invalid", pos }, pos];
}
foundDot = true;
}
if (this.input[endPos] === "0") {
only0s = endPos === pos ? true : only0s;
}
if (this.input[endPos] === "_" && (this.input[endPos - 1] === "_" || this.input[endPos - 1] === "." || only0s)) {
return [{ type: "invalid", pos }, pos];
}
endPos += 1;
}
private isDigit(char: string | undefined): char is `${number}` {
return /\d/.test(char?.[0] ?? "");
if (pos === endPos) {
return [{ type: "invalid", pos }, pos];
}
private getNumber(pos: number): [Token, number] {
let endPos = pos;
let foundDot = false;
let only0s = false;
while (
this.isDigit(this.input[endPos]) ||
this.input[endPos] === "." ||
(this.input[endPos] === "_" && endPos > pos)
) {
if (this.input[endPos] === ".") {
if (foundDot) {
return [{ type: "invalid", pos }, pos];
}
foundDot = true;
}
if (this.input[endPos] === "0") {
only0s = endPos === pos ? true : only0s;
}
if (
this.input[endPos] === "_" &&
(this.input[endPos - 1] === "_" || this.input[endPos - 1] === "." || only0s)
) {
return [{ type: "invalid", pos }, pos];
}
endPos += 1;
}
if (pos === endPos) {
return [{ type: "invalid", pos }, pos];
}
if (this.input[endPos - 1] === "_") {
return [{ type: "invalid", pos }, pos];
}
return [{ type: "number", symbol: this.input.slice(pos, endPos), pos }, endPos];
if (this.input[endPos - 1] === "_") {
return [{ type: "invalid", pos }, pos];
}
return [{ type: "number", symbol: this.input.slice(pos, endPos), pos }, endPos];
}
private isIdentifierStart(char: string | undefined) {
return /[$_\p{ID_Start}]/u.test(char?.[0] ?? "");
}
private isIdentifierStart(char: string | undefined) {
return /[$_\p{ID_Start}]/u.test(char?.[0] ?? "");
}
private isIdentifier(char: string | undefined) {
return /[$\u200c\u200d\p{ID_Continue}]/u.test(char?.[0] ?? "");
}
private isIdentifier(char: string | undefined) {
return /[$\u200c\u200d\p{ID_Continue}]/u.test(char?.[0] ?? "");
}
private getIdentifier(pos: number): [Token, number] {
let endPos = pos;
while (endPos < this.input.length && this.isIdentifier(this.input[endPos])) {
endPos += 1;
}
if (endPos === pos) {
return [{ type: "invalid", pos }, pos];
}
return [{ type: "iden", symbol: this.input.slice(pos, endPos), pos }, endPos];
private getIdentifier(pos: number): [Token, number] {
let endPos = pos;
while (endPos < this.input.length && this.isIdentifier(this.input[endPos])) {
endPos += 1;
}
if (endPos === pos) {
return [{ type: "invalid", pos }, pos];
}
return [{ type: "iden", symbol: this.input.slice(pos, endPos), pos }, endPos];
}
private getString(pos: number): [Token, number] {
const quote = this.input[pos];
let endPos = pos + 1;
let prev = this.input[pos];
while (endPos < this.input.length && (this.input[endPos] !== quote || prev === "\\")) {
prev = this.input[endPos];
endPos += 1;
}
if (endPos === pos || this.input[endPos] !== quote) {
return [{ type: "invalid", pos }, pos];
}
return [{ type: "string", symbol: this.input.slice(pos, endPos + 1), pos }, endPos + 1];
private getString(pos: number): [Token, number] {
const quote = this.input[pos];
let endPos = pos + 1;
let prev = this.input[pos];
while (endPos < this.input.length && (this.input[endPos] !== quote || prev === "\\")) {
prev = this.input[endPos];
endPos += 1;
}
if (endPos === pos || this.input[endPos] !== quote) {
return [{ type: "invalid", pos }, pos];
}
return [{ type: "string", symbol: this.input.slice(pos, endPos + 1), pos }, endPos + 1];
}
private isWhiteSpace(char: string | undefined) {
return /\s/.test(char?.[0] ?? "");
}
private isWhiteSpace(char: string | undefined) {
return /\s/.test(char?.[0] ?? "");
}
}

View file

@ -6,25 +6,25 @@ import { literals, safeOperators } from "./grammar";
import { Lexer } from "./lexer";
export class Validator {
constructor(private readonly predicate: (identifier: string) => boolean = Validator.defaultPredicate) {}
constructor(private readonly predicate: (identifier: string) => boolean = Validator.defaultPredicate) {}
static readonly defaultPredicate = (identifier: string) => [...literals, ...safeOperators].includes(identifier);
static readonly defaultPredicate = (identifier: string) => [...literals, ...safeOperators].includes(identifier);
public validate(input: string): void {
const lexer = new Lexer(input);
for (const token of lexer) {
if (token.type === "iden" && !this.predicate(token.symbol)) {
throw new ValidationError(token.symbol);
}
if (token.type === "invalid") {
throw new SyntaxError(`Invalid or unexpected token (${token.pos})`);
}
}
public validate(input: string): void {
const lexer = new Lexer(input);
for (const token of lexer) {
if (token.type === "iden" && !this.predicate(token.symbol)) {
throw new ValidationError(token.symbol);
}
if (token.type === "invalid") {
throw new SyntaxError(`Invalid or unexpected token (${token.pos})`);
}
}
}
}
class ValidationError extends Error {
constructor(identifier: string) {
super(`'${identifier}' is not an allowed identifier.`);
}
constructor(identifier: string) {
super(`'${identifier}' is not an allowed identifier.`);
}
}