-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support to modifiers after snap operators
Prior to this commit no further amount modifiers could be appended after snap operations (E.g: "now/d+1h"). This commit adds support for chaining modifiers with no limits. Regexes to parse content have been dropped in favor of two new elements: a lexer and a Pratt parser. Token are not as complex as a language, so recursion is not used as ast nodes can be stored in a sequential plain list. Some other features have been dropped as they were rather redudant. Should the number of amount modifiers be limited by the end user, the library should remain agnostic about. Features dropped: - `complex_token_to_date` util - `simple_token_to_date` util - `SimpleToken` model - `ComplexToken` model
- Loading branch information
Showing
70 changed files
with
2,058 additions
and
689 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { Token } from '../token'; | ||
export interface Expression { | ||
token: Token; | ||
operate(date?: Date): Date; | ||
toString(): string; | ||
} | ||
export declare class NowExpression implements Expression { | ||
token: Token; | ||
constructor(token: Token); | ||
operate(date: Date): Date; | ||
toString(): string; | ||
} | ||
export declare class ModifierExpression implements Expression { | ||
token: Token; | ||
amount: number; | ||
operator: string; | ||
modifier: string; | ||
constructor(token: Token, amount: number | undefined, operator: string, modifier: string); | ||
operate(date: Date): Date; | ||
toString(): string; | ||
} | ||
export declare class SnapExpression implements Expression { | ||
token: Token; | ||
modifier: string; | ||
operator: string; | ||
constructor(token: Token, modifier: string, operator: string); | ||
operate(date: Date): Date; | ||
toString(): string; | ||
} | ||
export declare namespace AmountModifiers { | ||
const valuesString: string; | ||
function checkModifier(modifier: string): boolean; | ||
} | ||
export declare namespace SnapModifiers { | ||
const valuesString: string; | ||
function checkModifier(modifier: string): boolean; | ||
} | ||
export declare function newNowExpression(): NowExpression; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var dateFn = require("date-fns"); | ||
var token_1 = require("../token"); | ||
var NowExpression = /** @class */ (function () { | ||
function NowExpression(token) { | ||
this.token = token; | ||
} | ||
NowExpression.prototype.operate = function (date) { | ||
return date; | ||
}; | ||
NowExpression.prototype.toString = function () { | ||
return this.token.literal; | ||
}; | ||
return NowExpression; | ||
}()); | ||
exports.NowExpression = NowExpression; | ||
var ModifierExpression = /** @class */ (function () { | ||
function ModifierExpression(token, amount, operator, modifier) { | ||
if (amount === void 0) { amount = 1; } | ||
this.token = token; | ||
this.amount = amount; | ||
this.operator = operator; | ||
this.modifier = modifier; | ||
} | ||
ModifierExpression.prototype.operate = function (date) { | ||
// Lazy enough for not to type nested objects | ||
switch (this.operator) { | ||
case token_1.TokenType.PLUS: | ||
switch (this.modifier) { | ||
case 's': | ||
return dateFn.addSeconds(date, this.amount); | ||
case 'm': | ||
return dateFn.addMinutes(date, this.amount); | ||
case 'h': | ||
return dateFn.addHours(date, this.amount); | ||
case 'd': | ||
return dateFn.addDays(date, this.amount); | ||
case 'w': | ||
return dateFn.addWeeks(date, this.amount); | ||
case 'M': | ||
return dateFn.addMonths(date, this.amount); | ||
} | ||
break; | ||
case token_1.TokenType.MINUS: | ||
switch (this.modifier) { | ||
case 's': | ||
return dateFn.subSeconds(date, this.amount); | ||
case 'm': | ||
return dateFn.subMinutes(date, this.amount); | ||
case 'h': | ||
return dateFn.subHours(date, this.amount); | ||
case 'd': | ||
return dateFn.subDays(date, this.amount); | ||
case 'w': | ||
return dateFn.subWeeks(date, this.amount); | ||
case 'M': | ||
return dateFn.subMonths(date, this.amount); | ||
} | ||
break; | ||
} | ||
return date; | ||
}; | ||
ModifierExpression.prototype.toString = function () { | ||
return "" + this.operator + this.amount + this.modifier; | ||
}; | ||
return ModifierExpression; | ||
}()); | ||
exports.ModifierExpression = ModifierExpression; | ||
var SnapExpression = /** @class */ (function () { | ||
function SnapExpression(token, modifier, operator) { | ||
this.token = token; | ||
this.modifier = modifier; | ||
this.operator = operator; | ||
} | ||
SnapExpression.prototype.operate = function (date) { | ||
// Lazy enough for not to type nested objects | ||
switch (this.operator) { | ||
case token_1.TokenType.SLASH: | ||
switch (this.modifier) { | ||
case 's': | ||
return dateFn.startOfSecond(date); | ||
case 'm': | ||
return dateFn.startOfMinute(date); | ||
case 'h': | ||
return dateFn.startOfHour(date); | ||
case 'd': | ||
return dateFn.startOfDay(date); | ||
case 'w': | ||
case 'bw': | ||
return dateFn.startOfWeek(date); | ||
case 'M': | ||
return dateFn.startOfMonth(date); | ||
} | ||
break; | ||
case token_1.TokenType.AT: | ||
switch (this.modifier) { | ||
case 's': | ||
return dateFn.endOfSecond(date); | ||
case 'm': | ||
return dateFn.endOfMinute(date); | ||
case 'h': | ||
return dateFn.endOfHour(date); | ||
case 'd': | ||
return dateFn.endOfDay(date); | ||
case 'w': | ||
return dateFn.endOfWeek(date); | ||
case 'M': | ||
return dateFn.endOfMonth(date); | ||
case 'bw': { | ||
if (dateFn.isThisWeek(date) && !dateFn.isWeekend(date)) { | ||
return date; | ||
} | ||
return dateFn.endOfDay(dateFn.addDays(dateFn.startOfWeek(date), 5)); | ||
} | ||
} | ||
break; | ||
} | ||
return date; | ||
}; | ||
SnapExpression.prototype.toString = function () { | ||
return "" + this.operator + this.modifier; | ||
}; | ||
return SnapExpression; | ||
}()); | ||
exports.SnapExpression = SnapExpression; | ||
var AmountModifiers; | ||
(function (AmountModifiers) { | ||
var values = ['s', 'm', 'h', 'd', 'w', 'M']; | ||
AmountModifiers.valuesString = "(" + values.map(function (v) { return "\"" + v + "\""; }).join(',') + ")"; | ||
function checkModifier(modifier) { | ||
return values.includes(modifier); | ||
} | ||
AmountModifiers.checkModifier = checkModifier; | ||
})(AmountModifiers = exports.AmountModifiers || (exports.AmountModifiers = {})); | ||
var SnapModifiers; | ||
(function (SnapModifiers) { | ||
var values = ['s', 'm', 'h', 'd', 'w', 'bw', 'M']; | ||
SnapModifiers.valuesString = "(" + values.map(function (v) { return "\"" + v + "\""; }).join(',') + ")"; | ||
function checkModifier(modifier) { | ||
return values.includes(modifier); | ||
} | ||
SnapModifiers.checkModifier = checkModifier; | ||
})(SnapModifiers = exports.SnapModifiers || (exports.SnapModifiers = {})); | ||
function newNowExpression() { | ||
return new NowExpression(new token_1.Token(token_1.TokenType.NOW, 'now')); | ||
} | ||
exports.newNowExpression = newNowExpression; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './ast'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
"use strict"; | ||
function __export(m) { | ||
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; | ||
} | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
__export(require("./ast")); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export declare class InvalidTokenError extends Error { | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
"use strict"; | ||
var __extends = (this && this.__extends) || (function () { | ||
var extendStatics = function (d, b) { | ||
extendStatics = Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; | ||
return extendStatics(d, b); | ||
}; | ||
return function (d, b) { | ||
extendStatics(d, b); | ||
function __() { this.constructor = d; } | ||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
}; | ||
})(); | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var InvalidTokenError = /** @class */ (function (_super) { | ||
__extends(InvalidTokenError, _super); | ||
function InvalidTokenError() { | ||
return _super !== null && _super.apply(this, arguments) || this; | ||
} | ||
return InvalidTokenError; | ||
}(Error)); | ||
exports.InvalidTokenError = InvalidTokenError; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export { Token } from './models'; | ||
export { tokenToDate } from './utils'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var models_1 = require("./models"); | ||
exports.Token = models_1.Token; | ||
var utils_1 = require("./utils"); | ||
exports.tokenToDate = utils_1.tokenToDate; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './lexer'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
"use strict"; | ||
function __export(m) { | ||
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; | ||
} | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
__export(require("./lexer")); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { Token } from '../token'; | ||
export declare class Lexer { | ||
private position; | ||
private readPosition; | ||
private readonly input; | ||
private currentChar; | ||
constructor(input?: string); | ||
nextToken(): Token; | ||
private readChar; | ||
private peekChar; | ||
private readNumber; | ||
private readWord; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var token_1 = require("../token"); | ||
function isDigit(payload) { | ||
return /^\d+$/.test(payload); | ||
} | ||
function isLetter(payload) { | ||
return /^\w+$/.test(payload); | ||
} | ||
var Lexer = /** @class */ (function () { | ||
function Lexer(input) { | ||
if (input === void 0) { input = ''; } | ||
this.position = this.readPosition = 0; | ||
this.input = input.trim(); | ||
this.currentChar = ''; | ||
this.readChar(); | ||
} | ||
Lexer.prototype.nextToken = function () { | ||
var token; | ||
if (this.currentChar === '') { | ||
return new token_1.Token(token_1.TokenType.END, this.currentChar); | ||
} | ||
else if (this.currentChar === '+') { | ||
token = new token_1.Token(token_1.TokenType.PLUS, this.currentChar); | ||
} | ||
else if (this.currentChar === '-') { | ||
token = new token_1.Token(token_1.TokenType.MINUS, this.currentChar); | ||
} | ||
else if (this.currentChar === '/') { | ||
token = new token_1.Token(token_1.TokenType.SLASH, this.currentChar); | ||
} | ||
else if (this.currentChar === '@') { | ||
token = new token_1.Token(token_1.TokenType.AT, this.currentChar); | ||
} | ||
else if (isDigit(this.currentChar)) { | ||
return new token_1.Token(token_1.TokenType.NUMBER, this.readNumber()); | ||
} | ||
else if (isLetter(this.currentChar)) { | ||
var literal = this.readWord(); | ||
return new token_1.Token(token_1.lookupIdentifier(literal), literal); | ||
} | ||
else { | ||
token = new token_1.Token(token_1.TokenType.ILLEGAL, this.currentChar); | ||
} | ||
this.readChar(); | ||
return token; | ||
}; | ||
Lexer.prototype.readChar = function () { | ||
if (this.position >= this.input.length) { | ||
this.readPosition = 0; | ||
this.currentChar = ''; | ||
} | ||
else { | ||
this.currentChar = this.input[this.readPosition]; | ||
this.position = this.readPosition; | ||
} | ||
this.readPosition++; | ||
}; | ||
Lexer.prototype.peekChar = function () { | ||
if (this.position >= this.input.length) { | ||
return ''; | ||
} | ||
return this.input[this.readPosition]; | ||
}; | ||
Lexer.prototype.readNumber = function () { | ||
var pos = this.position; | ||
while (isDigit(this.currentChar)) { | ||
this.readChar(); | ||
} | ||
return this.input.substring(pos, this.position); | ||
}; | ||
Lexer.prototype.readWord = function () { | ||
var pos = this.position; | ||
while (isLetter(this.currentChar)) { | ||
this.readChar(); | ||
} | ||
return this.input.substring(pos, this.position); | ||
}; | ||
return Lexer; | ||
}()); | ||
exports.Lexer = Lexer; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export {}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var lexer_1 = require("./lexer"); | ||
var token_1 = require("../token"); | ||
describe('Lexer', function () { | ||
it('Lexer.nextToken tokenize ok', function () { | ||
var input = 'now-1h/h@M+2w/bw-3s-49d/m'; | ||
var lexer = new lexer_1.Lexer(input); | ||
var expected = [ | ||
[token_1.TokenType.NOW, 'now'], | ||
[token_1.TokenType.MINUS, '-'], | ||
[token_1.TokenType.NUMBER, '1'], | ||
[token_1.TokenType.MODIFIER, 'h'], | ||
[token_1.TokenType.SLASH, '/'], | ||
[token_1.TokenType.MODIFIER, 'h'], | ||
[token_1.TokenType.AT, '@'], | ||
[token_1.TokenType.MODIFIER, 'M'], | ||
[token_1.TokenType.PLUS, '+'], | ||
[token_1.TokenType.NUMBER, '2'], | ||
[token_1.TokenType.MODIFIER, 'w'], | ||
[token_1.TokenType.SLASH, '/'], | ||
[token_1.TokenType.MODIFIER, 'bw'], | ||
[token_1.TokenType.MINUS, '-'], | ||
[token_1.TokenType.NUMBER, '3'], | ||
[token_1.TokenType.MODIFIER, 's'], | ||
[token_1.TokenType.MINUS, '-'], | ||
[token_1.TokenType.NUMBER, '49'], | ||
[token_1.TokenType.MODIFIER, 'd'], | ||
[token_1.TokenType.SLASH, '/'], | ||
[token_1.TokenType.MODIFIER, 'm'], | ||
[token_1.TokenType.END, ''], | ||
]; | ||
for (var _i = 0, expected_1 = expected; _i < expected_1.length; _i++) { | ||
var expectedNode = expected_1[_i]; | ||
var actual = lexer.nextToken(); | ||
expect(actual.type).toBe(expectedNode[0]); | ||
expect(actual.literal).toBe(expectedNode[1]); | ||
} | ||
}); | ||
it('Lexer.nextToken tokenize illegal', function () { | ||
var input = 'now*2h'; | ||
var lexer = new lexer_1.Lexer(input); | ||
var expected = [ | ||
[token_1.TokenType.NOW, 'now'], | ||
[token_1.TokenType.ILLEGAL, '*'], | ||
[token_1.TokenType.NUMBER, '2'], | ||
[token_1.TokenType.MODIFIER, 'h'], | ||
]; | ||
for (var _i = 0, expected_2 = expected; _i < expected_2.length; _i++) { | ||
var expectedNode = expected_2[_i]; | ||
var actual = lexer.nextToken(); | ||
expect(actual.type).toBe(expectedNode[0]); | ||
expect(actual.literal).toBe(expectedNode[1]); | ||
} | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './models'; |
Oops, something went wrong.