@silane/datetime
Version:
Date and time library similar to Python's "datetime" package.
370 lines (368 loc) • 10 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.SyntaxDtexprDateTimeError = exports.ExecutionDtexprDateTimeError = exports.DtexprDateTimeError = void 0;
exports.dtexpr = dtexpr;
var _datetime = require("./datetime.cjs");
var _errors = require("./errors.cjs");
/**
* Base exception of other exceptions raised in dtexpr.
*/
class DtexprDateTimeError extends _errors.DateTimeError {
/**
* @param {*[]} expression The expression caused this error.
* @param {[number, number]} pos Position of the error in the expression.
* @param {string} message Error message.
*/
constructor(expression, pos, message) {
super(message);
this.expression = expression;
this.pos = pos;
}
toString() {
const [pos1, pos2] = this.pos;
const expressionStr = this.expression.map(x => typeof x === 'string' ? x : '${...}').join('');
const pos = this.expression.slice(0, pos1).map(x => typeof x === 'string' ? x.length : 6).reduce((acc, cur) => acc + cur, 0) + pos2;
return `[${this.constructor.name}] ${this.message}
${expressionStr}
${' '.repeat(pos)}^`;
}
}
/**
* Raised when there is a syntax error in dtexpr.
*/
exports.DtexprDateTimeError = DtexprDateTimeError;
class SyntaxDtexprDateTimeError extends DtexprDateTimeError {}
/**
* Raised when there occur an error in execution phase in dtexpr, such as
* inappropriate type passed to an operator.
*/
exports.SyntaxDtexprDateTimeError = SyntaxDtexprDateTimeError;
class ExecutionDtexprDateTimeError extends DtexprDateTimeError {
/**
* @param {*[]} expression The expression caused this error.
* @param {[number, number]} pos Position of the error in the expression.
* @param {?Error} originalError The original error object.
* @param {string} message Error message.
*/
constructor(expression, pos, originalError, message) {
super(expression, pos, message);
this.originalError = originalError;
}
toString() {
if (this.originalError) {
return `${super.toString()}
Original Error: ${this.originalError}`;
} else {
return super.toString();
}
}
}
exports.ExecutionDtexprDateTimeError = ExecutionDtexprDateTimeError;
class Variable {
constructor(name) {
this.name = name;
}
}
class Node {
constructor(pos) {
this.pos = pos;
}
execute(context) {
throw new _errors.NotImplementedDateTimeError();
}
}
class VariableNode extends Node {
constructor(variableName, pos) {
super(pos);
this.variableName = variableName;
}
execute(context) {
if (this.variableName in context.variables) {
return context.variables[this.variableName];
} else {
throw new ExecutionDtexprDateTimeError([], this.pos, null, `Varibale "${this.variableName}" is not defined in the execution context`);
}
}
}
class NegNode extends Node {
constructor(node, pos) {
super(pos);
this.node = node;
}
execute(context) {
const value = this.node.execute(context);
try {
return (0, _datetime.neg)(value);
} catch (e) {
if (e instanceof _errors.DateTimeError) {
throw new ExecutionDtexprDateTimeError([], this.pos, e, 'Execution error in negation operator.');
}
throw e;
}
}
}
class BinaryNode extends Node {
constructor(lhs, rhs, pos) {
super(pos);
this.lhs = lhs;
this.rhs = rhs;
}
}
class CommonBinaryNode extends BinaryNode {
/** @type {string} */
get operatorName() {
throw new _errors.NotImplementedDateTimeError();
}
operate(leftValue, rightValue) {
throw new _errors.NotImplementedDateTimeError();
}
execute(context) {
const lVal = this.lhs.execute(context);
const rVal = this.rhs.execute(context);
try {
return this.operate(lVal, rVal);
} catch (e) {
if (e instanceof _errors.DateTimeError) {
throw new ExecutionDtexprDateTimeError([], this.pos, e, `Execution error in ${this.operatorName} operator.`);
}
throw e;
}
}
}
class LesserNode extends CommonBinaryNode {
get operatorName() {
return 'lesser-than';
}
operate(leftValue, rightValue) {
return (0, _datetime.cmp)(leftValue, rightValue) < 0;
}
}
class LesserEqualNode extends CommonBinaryNode {
get operatorName() {
return 'lesser-or-equal';
}
operate(leftValue, rightValue) {
return (0, _datetime.cmp)(leftValue, rightValue) <= 0;
}
}
class EqualNode extends CommonBinaryNode {
get operatorName() {
return 'equal';
}
operate(leftValue, rightValue) {
return (0, _datetime.cmp)(leftValue, rightValue) == 0;
}
}
class NotEqualNode extends CommonBinaryNode {
get operatorName() {
return 'not-equal';
}
operate(leftValue, rightValue) {
return (0, _datetime.cmp)(leftValue, rightValue) != 0;
}
}
class GreaterNode extends CommonBinaryNode {
get operatorName() {
return 'greater-than';
}
operate(leftValue, rightValue) {
return (0, _datetime.cmp)(leftValue, rightValue) > 0;
}
}
class GreaterEqualNode extends CommonBinaryNode {
get operatorName() {
return 'greater-or-equal';
}
operate(leftValue, rightValue) {
return (0, _datetime.cmp)(leftValue, rightValue) >= 0;
}
}
class AddNode extends CommonBinaryNode {
get operatorName() {
return 'addition';
}
operate(leftValue, rightValue) {
return (0, _datetime.add)(leftValue, rightValue);
}
}
class SubNode extends CommonBinaryNode {
get operatorName() {
return 'subtraction';
}
operate(leftValue, rightValue) {
return (0, _datetime.sub)(leftValue, rightValue);
}
}
class ParsingStr {
/**
* @param {(string|number|!TimeDelta|!Date|!Time|!DateTime)[]} s
*/
constructor(s) {
this.s = s;
this.pos1 = 0;
this.pos2 = 0;
}
consumeIf(startstr) {
const item = this.s[this.pos1];
if (typeof item === 'string') {
if (item.slice(this.pos2).startsWith(startstr)) {
this.pos2 += startstr.length;
if (this.pos2 >= item.length) {
++this.pos1;
this.pos2 = 0;
}
return startstr;
} else {
return '';
}
} else {
return '';
}
}
consumeWhile(chars) {
const item = this.s[this.pos1];
if (typeof item === 'string') {
let ret = '';
for (; this.pos2 < item.length; ++this.pos2) {
if (chars.includes(this.s[this.pos1][this.pos2])) ret += this.s[this.pos1][this.pos2];else break;
}
if (this.pos2 >= item.length) {
++this.pos1;
this.pos2 = 0;
}
return ret;
} else {
return '';
}
}
consumeIfVariable() {
if (this.s[this.pos1] instanceof Variable) {
return this.s[this.pos1++];
} else {
return null;
}
}
}
function whitespace(s) {
s.consumeWhile(' \n');
}
function expr(s) {
whitespace(s);
const lhs = poly(s);
const pos = [s.pos1, s.pos2];
if (s.consumeIf('<=')) {
whitespace(s);
const rhs = expr(s);
return new LesserEqualNode(lhs, rhs, pos);
} else if (s.consumeIf('<')) {
whitespace(s);
const rhs = expr(s);
return new LesserNode(lhs, rhs, pos);
} else if (s.consumeIf('==')) {
whitespace(s);
const rhs = expr(s);
return new EqualNode(lhs, rhs, pos);
} else if (s.consumeIf('!=')) {
whitespace(s);
const rhs = expr(s);
return new NotEqualNode(lhs, rhs, pos);
} else if (s.consumeIf('>=')) {
whitespace(s);
const rhs = expr(s);
return new GreaterEqualNode(lhs, rhs, pos);
} else if (s.consumeIf('>')) {
whitespace(s);
const rhs = expr(s);
return new GreaterNode(lhs, rhs, pos);
} else {
return lhs;
}
}
function poly(s) {
whitespace(s);
const lhs = term(s);
whitespace(s);
const pos = [s.pos1, s.pos2];
if (s.consumeIf('+')) {
whitespace(s);
const rhs = poly(s);
return new AddNode(lhs, rhs, pos);
} else if (s.consumeIf('-')) {
whitespace(s);
const rhs = poly(s);
return new SubNode(lhs, rhs, pos);
} else {
return lhs;
}
}
function term(s) {
return negterm(s);
}
function negterm(s) {
whitespace(s);
const pos = [s.pos1, s.pos2];
if (s.consumeIf('-')) {
whitespace(s);
const node = realexpr(s);
return new NegNode(node, pos);
} else {
return realexpr(s);
}
}
function realexpr(s) {
whitespace(s);
/** @type {[number, number]} */
const pos = [s.pos1, s.pos2];
if (s.consumeIf('(')) {
whitespace(s);
const lhs = poly(s);
if (!s.consumeIf(')')) {
throw new SyntaxDtexprDateTimeError(s.s, pos, 'Expected ")".');
}
return lhs;
}
const variable = s.consumeIfVariable();
if (variable != null) {
return new VariableNode(variable.name, pos);
}
throw new SyntaxDtexprDateTimeError(s.s, pos, 'Unexpected token.');
}
function isExpressionTokenListSame(a, b) {
if (a.length !== b.length) return false;
return a.map((x, i) => [x, b[i]]).every(([aToken, bToken]) => {
if (aToken === bToken) return true;
return aToken instanceof Variable && bToken instanceof Variable && aToken.name === bToken.name;
});
}
const expressionCache = [];
/**
* Tagged template function to perform operations on datetime objects.
* @param {string[]} strings Strings to be passed by tagged template.
* @param {...*} values Values to be passed by tagged template.
*/
function dtexpr(strings, ...values) {
const variables = values.map((x, i) => [new Variable(`var_${i}`), x]);
let list = [strings[0]];
variables.forEach((variable, i) => {
list.push(variable[0]);
list.push(strings[i + 1]);
});
list = list.filter(x => !(typeof x === 'string' && !x));
let expression = expressionCache.find(x => isExpressionTokenListSame(x[0], list))?.[1] ?? null;
if (!expression) {
expression = expr(new ParsingStr(list));
expressionCache.push([list, expression]);
}
try {
return expression.execute({
variables: Object.fromEntries(variables.map(x => [x[0].name, x[1]]))
});
} catch (e) {
if (e instanceof ExecutionDtexprDateTimeError) {
e.expression = list;
}
throw e;
}
}