mirror of
https://github.com/swc-project/swc.git
synced 2024-12-01 09:52:57 +03:00
323 lines
9.7 KiB
TypeScript
323 lines
9.7 KiB
TypeScript
// Loaded from https://unpkg.com/luxon@1.25.0/src/impl/regexParser.js
|
|
|
|
|
|
import {
|
|
untruncateYear,
|
|
signedOffset,
|
|
parseInteger,
|
|
parseMillis,
|
|
ianaRegex,
|
|
isUndefined
|
|
} from "./util.js";
|
|
import * as English from "./english.js";
|
|
import FixedOffsetZone from "../zones/fixedOffsetZone.js";
|
|
import IANAZone from "../zones/IANAZone.js";
|
|
|
|
/*
|
|
* This file handles parsing for well-specified formats. Here's how it works:
|
|
* Two things go into parsing: a regex to match with and an extractor to take apart the groups in the match.
|
|
* An extractor is just a function that takes a regex match array and returns a { year: ..., month: ... } object
|
|
* parse() does the work of executing the regex and applying the extractor. It takes multiple regex/extractor pairs to try in sequence.
|
|
* Extractors can take a "cursor" representing the offset in the match to look at. This makes it easy to combine extractors.
|
|
* combineExtractors() does the work of combining them, keeping track of the cursor through multiple extractions.
|
|
* Some extractions are super dumb and simpleParse and fromStrings help DRY them.
|
|
*/
|
|
|
|
function combineRegexes(...regexes) {
|
|
const full = regexes.reduce((f, r) => f + r.source, "");
|
|
return RegExp(`^${full}$`);
|
|
}
|
|
|
|
function combineExtractors(...extractors) {
|
|
return m =>
|
|
extractors
|
|
.reduce(
|
|
([mergedVals, mergedZone, cursor], ex) => {
|
|
const [val, zone, next] = ex(m, cursor);
|
|
return [Object.assign(mergedVals, val), mergedZone || zone, next];
|
|
},
|
|
[{}, null, 1]
|
|
)
|
|
.slice(0, 2);
|
|
}
|
|
|
|
function parse(s, ...patterns) {
|
|
if (s == null) {
|
|
return [null, null];
|
|
}
|
|
|
|
for (const [regex, extractor] of patterns) {
|
|
const m = regex.exec(s);
|
|
if (m) {
|
|
return extractor(m);
|
|
}
|
|
}
|
|
return [null, null];
|
|
}
|
|
|
|
function simpleParse(...keys) {
|
|
return (match, cursor) => {
|
|
const ret = {};
|
|
let i;
|
|
|
|
for (i = 0; i < keys.length; i++) {
|
|
ret[keys[i]] = parseInteger(match[cursor + i]);
|
|
}
|
|
return [ret, null, cursor + i];
|
|
};
|
|
}
|
|
|
|
// ISO and SQL parsing
|
|
const offsetRegex = /(?:(Z)|([+-]\d\d)(?::?(\d\d))?)/,
|
|
isoTimeBaseRegex = /(\d\d)(?::?(\d\d)(?::?(\d\d)(?:[.,](\d{1,30}))?)?)?/,
|
|
isoTimeRegex = RegExp(`${isoTimeBaseRegex.source}${offsetRegex.source}?`),
|
|
isoTimeExtensionRegex = RegExp(`(?:T${isoTimeRegex.source})?`),
|
|
isoYmdRegex = /([+-]\d{6}|\d{4})(?:-?(\d\d)(?:-?(\d\d))?)?/,
|
|
isoWeekRegex = /(\d{4})-?W(\d\d)(?:-?(\d))?/,
|
|
isoOrdinalRegex = /(\d{4})-?(\d{3})/,
|
|
extractISOWeekData = simpleParse("weekYear", "weekNumber", "weekDay"),
|
|
extractISOOrdinalData = simpleParse("year", "ordinal"),
|
|
sqlYmdRegex = /(\d{4})-(\d\d)-(\d\d)/, // dumbed-down version of the ISO one
|
|
sqlTimeRegex = RegExp(
|
|
`${isoTimeBaseRegex.source} ?(?:${offsetRegex.source}|(${ianaRegex.source}))?`
|
|
),
|
|
sqlTimeExtensionRegex = RegExp(`(?: ${sqlTimeRegex.source})?`);
|
|
|
|
function int(match, pos, fallback) {
|
|
const m = match[pos];
|
|
return isUndefined(m) ? fallback : parseInteger(m);
|
|
}
|
|
|
|
function extractISOYmd(match, cursor) {
|
|
const item = {
|
|
year: int(match, cursor),
|
|
month: int(match, cursor + 1, 1),
|
|
day: int(match, cursor + 2, 1)
|
|
};
|
|
|
|
return [item, null, cursor + 3];
|
|
}
|
|
|
|
function extractISOTime(match, cursor) {
|
|
const item = {
|
|
hour: int(match, cursor, 0),
|
|
minute: int(match, cursor + 1, 0),
|
|
second: int(match, cursor + 2, 0),
|
|
millisecond: parseMillis(match[cursor + 3])
|
|
};
|
|
|
|
return [item, null, cursor + 4];
|
|
}
|
|
|
|
function extractISOOffset(match, cursor) {
|
|
const local = !match[cursor] && !match[cursor + 1],
|
|
fullOffset = signedOffset(match[cursor + 1], match[cursor + 2]),
|
|
zone = local ? null : FixedOffsetZone.instance(fullOffset);
|
|
return [{}, zone, cursor + 3];
|
|
}
|
|
|
|
function extractIANAZone(match, cursor) {
|
|
const zone = match[cursor] ? IANAZone.create(match[cursor]) : null;
|
|
return [{}, zone, cursor + 1];
|
|
}
|
|
|
|
// ISO duration parsing
|
|
|
|
const isoDuration = /^-?P(?:(?:(-?\d{1,9})Y)?(?:(-?\d{1,9})M)?(?:(-?\d{1,9})W)?(?:(-?\d{1,9})D)?(?:T(?:(-?\d{1,9})H)?(?:(-?\d{1,9})M)?(?:(-?\d{1,20})(?:[.,](-?\d{1,9}))?S)?)?)$/;
|
|
|
|
function extractISODuration(match) {
|
|
const [
|
|
s,
|
|
yearStr,
|
|
monthStr,
|
|
weekStr,
|
|
dayStr,
|
|
hourStr,
|
|
minuteStr,
|
|
secondStr,
|
|
millisecondsStr
|
|
] = match;
|
|
|
|
const hasNegativePrefix = s[0] === "-";
|
|
|
|
const maybeNegate = num => (num && hasNegativePrefix ? -num : num);
|
|
|
|
return [
|
|
{
|
|
years: maybeNegate(parseInteger(yearStr)),
|
|
months: maybeNegate(parseInteger(monthStr)),
|
|
weeks: maybeNegate(parseInteger(weekStr)),
|
|
days: maybeNegate(parseInteger(dayStr)),
|
|
hours: maybeNegate(parseInteger(hourStr)),
|
|
minutes: maybeNegate(parseInteger(minuteStr)),
|
|
seconds: maybeNegate(parseInteger(secondStr)),
|
|
milliseconds: maybeNegate(parseMillis(millisecondsStr))
|
|
}
|
|
];
|
|
}
|
|
|
|
// These are a little braindead. EDT *should* tell us that we're in, say, America/New_York
|
|
// and not just that we're in -240 *right now*. But since I don't think these are used that often
|
|
// I'm just going to ignore that
|
|
const obsOffsets = {
|
|
GMT: 0,
|
|
EDT: -4 * 60,
|
|
EST: -5 * 60,
|
|
CDT: -5 * 60,
|
|
CST: -6 * 60,
|
|
MDT: -6 * 60,
|
|
MST: -7 * 60,
|
|
PDT: -7 * 60,
|
|
PST: -8 * 60
|
|
};
|
|
|
|
function fromStrings(weekdayStr, yearStr, monthStr, dayStr, hourStr, minuteStr, secondStr) {
|
|
const result = {
|
|
year: yearStr.length === 2 ? untruncateYear(parseInteger(yearStr)) : parseInteger(yearStr),
|
|
month: English.monthsShort.indexOf(monthStr) + 1,
|
|
day: parseInteger(dayStr),
|
|
hour: parseInteger(hourStr),
|
|
minute: parseInteger(minuteStr)
|
|
};
|
|
|
|
if (secondStr) result.second = parseInteger(secondStr);
|
|
if (weekdayStr) {
|
|
result.weekday =
|
|
weekdayStr.length > 3
|
|
? English.weekdaysLong.indexOf(weekdayStr) + 1
|
|
: English.weekdaysShort.indexOf(weekdayStr) + 1;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// RFC 2822/5322
|
|
const rfc2822 = /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|(?:([+-]\d\d)(\d\d)))$/;
|
|
|
|
function extractRFC2822(match) {
|
|
const [
|
|
,
|
|
weekdayStr,
|
|
dayStr,
|
|
monthStr,
|
|
yearStr,
|
|
hourStr,
|
|
minuteStr,
|
|
secondStr,
|
|
obsOffset,
|
|
milOffset,
|
|
offHourStr,
|
|
offMinuteStr
|
|
] = match,
|
|
result = fromStrings(weekdayStr, yearStr, monthStr, dayStr, hourStr, minuteStr, secondStr);
|
|
|
|
let offset;
|
|
if (obsOffset) {
|
|
offset = obsOffsets[obsOffset];
|
|
} else if (milOffset) {
|
|
offset = 0;
|
|
} else {
|
|
offset = signedOffset(offHourStr, offMinuteStr);
|
|
}
|
|
|
|
return [result, new FixedOffsetZone(offset)];
|
|
}
|
|
|
|
function preprocessRFC2822(s) {
|
|
// Remove comments and folding whitespace and replace multiple-spaces with a single space
|
|
return s
|
|
.replace(/\([^)]*\)|[\n\t]/g, " ")
|
|
.replace(/(\s\s+)/g, " ")
|
|
.trim();
|
|
}
|
|
|
|
// http date
|
|
|
|
const rfc1123 = /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), (\d\d) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (\d{4}) (\d\d):(\d\d):(\d\d) GMT$/,
|
|
rfc850 = /^(Monday|Tuesday|Wedsday|Thursday|Friday|Saturday|Sunday), (\d\d)-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-(\d\d) (\d\d):(\d\d):(\d\d) GMT$/,
|
|
ascii = /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ( \d|\d\d) (\d\d):(\d\d):(\d\d) (\d{4})$/;
|
|
|
|
function extractRFC1123Or850(match) {
|
|
const [, weekdayStr, dayStr, monthStr, yearStr, hourStr, minuteStr, secondStr] = match,
|
|
result = fromStrings(weekdayStr, yearStr, monthStr, dayStr, hourStr, minuteStr, secondStr);
|
|
return [result, FixedOffsetZone.utcInstance];
|
|
}
|
|
|
|
function extractASCII(match) {
|
|
const [, weekdayStr, monthStr, dayStr, hourStr, minuteStr, secondStr, yearStr] = match,
|
|
result = fromStrings(weekdayStr, yearStr, monthStr, dayStr, hourStr, minuteStr, secondStr);
|
|
return [result, FixedOffsetZone.utcInstance];
|
|
}
|
|
|
|
const isoYmdWithTimeExtensionRegex = combineRegexes(isoYmdRegex, isoTimeExtensionRegex);
|
|
const isoWeekWithTimeExtensionRegex = combineRegexes(isoWeekRegex, isoTimeExtensionRegex);
|
|
const isoOrdinalWithTimeExtensionRegex = combineRegexes(isoOrdinalRegex, isoTimeExtensionRegex);
|
|
const isoTimeCombinedRegex = combineRegexes(isoTimeRegex);
|
|
|
|
const extractISOYmdTimeAndOffset = combineExtractors(
|
|
extractISOYmd,
|
|
extractISOTime,
|
|
extractISOOffset
|
|
);
|
|
const extractISOWeekTimeAndOffset = combineExtractors(
|
|
extractISOWeekData,
|
|
extractISOTime,
|
|
extractISOOffset
|
|
);
|
|
const extractISOOrdinalDataAndTime = combineExtractors(extractISOOrdinalData, extractISOTime);
|
|
const extractISOTimeAndOffset = combineExtractors(extractISOTime, extractISOOffset);
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
|
|
export function parseISODate(s) {
|
|
return parse(
|
|
s,
|
|
[isoYmdWithTimeExtensionRegex, extractISOYmdTimeAndOffset],
|
|
[isoWeekWithTimeExtensionRegex, extractISOWeekTimeAndOffset],
|
|
[isoOrdinalWithTimeExtensionRegex, extractISOOrdinalDataAndTime],
|
|
[isoTimeCombinedRegex, extractISOTimeAndOffset]
|
|
);
|
|
}
|
|
|
|
export function parseRFC2822Date(s) {
|
|
return parse(preprocessRFC2822(s), [rfc2822, extractRFC2822]);
|
|
}
|
|
|
|
export function parseHTTPDate(s) {
|
|
return parse(
|
|
s,
|
|
[rfc1123, extractRFC1123Or850],
|
|
[rfc850, extractRFC1123Or850],
|
|
[ascii, extractASCII]
|
|
);
|
|
}
|
|
|
|
export function parseISODuration(s) {
|
|
return parse(s, [isoDuration, extractISODuration]);
|
|
}
|
|
|
|
const sqlYmdWithTimeExtensionRegex = combineRegexes(sqlYmdRegex, sqlTimeExtensionRegex);
|
|
const sqlTimeCombinedRegex = combineRegexes(sqlTimeRegex);
|
|
|
|
const extractISOYmdTimeOffsetAndIANAZone = combineExtractors(
|
|
extractISOYmd,
|
|
extractISOTime,
|
|
extractISOOffset,
|
|
extractIANAZone
|
|
);
|
|
const extractISOTimeOffsetAndIANAZone = combineExtractors(
|
|
extractISOTime,
|
|
extractISOOffset,
|
|
extractIANAZone
|
|
);
|
|
|
|
export function parseSQL(s) {
|
|
return parse(
|
|
s,
|
|
[sqlYmdWithTimeExtensionRegex, extractISOYmdTimeOffsetAndIANAZone],
|
|
[sqlTimeCombinedRegex, extractISOTimeOffsetAndIANAZone]
|
|
);
|
|
}
|