grammar-composer 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +144 -89
- package/dist/exports/Exports.d.ts +2 -0
- package/dist/exports/Exports.d.ts.map +1 -1
- package/dist/exports/Exports.js +2 -0
- package/dist/exports/Exports.js.map +1 -1
- package/dist/parser-generator/Grammar.d.ts +8 -1
- package/dist/parser-generator/Grammar.d.ts.map +1 -1
- package/dist/parser-generator/Grammar.js +30 -225
- package/dist/parser-generator/Grammar.js.map +1 -1
- package/dist/parser-generator/ParseError.d.ts +37 -0
- package/dist/parser-generator/ParseError.d.ts.map +1 -0
- package/dist/parser-generator/ParseError.js +226 -0
- package/dist/parser-generator/ParseError.js.map +1 -0
- package/dist/parser-generator/StaticAnalysis.d.ts +6 -0
- package/dist/parser-generator/StaticAnalysis.d.ts.map +1 -0
- package/dist/parser-generator/StaticAnalysis.js +283 -0
- package/dist/parser-generator/StaticAnalysis.js.map +1 -0
- package/dist/parser-generator/TerminalToText.d.ts +15 -0
- package/dist/parser-generator/TerminalToText.d.ts.map +1 -0
- package/dist/parser-generator/TerminalToText.js +158 -0
- package/dist/parser-generator/TerminalToText.js.map +1 -0
- package/dist/parser-generator/TopDownParser.d.ts +4 -2
- package/dist/parser-generator/TopDownParser.d.ts.map +1 -1
- package/dist/parser-generator/TopDownParser.js +44 -42
- package/dist/parser-generator/TopDownParser.js.map +1 -1
- package/dist/tests/Test.js +23 -23
- package/dist/tests/Test.js.map +1 -1
- package/dist/tests/test-grammars/JsonGrammar.d.ts +12 -11
- package/dist/tests/test-grammars/JsonGrammar.d.ts.map +1 -1
- package/dist/tests/test-grammars/JsonGrammar.js +25 -10
- package/dist/tests/test-grammars/JsonGrammar.js.map +1 -1
- package/dist/tests/test-grammars/RegExpGrammar.d.ts +25 -25
- package/dist/tests/test-grammars/RegExpGrammar.d.ts.map +1 -1
- package/dist/tests/test-grammars/RegExpGrammar.js +48 -31
- package/dist/tests/test-grammars/RegExpGrammar.js.map +1 -1
- package/dist/tests/test-grammars/XmlGrammar.d.ts +11 -10
- package/dist/tests/test-grammars/XmlGrammar.d.ts.map +1 -1
- package/dist/tests/test-grammars/XmlGrammar.js +19 -8
- package/dist/tests/test-grammars/XmlGrammar.js.map +1 -1
- package/dist/utilities/LineAndColumn.d.ts +7 -0
- package/dist/utilities/LineAndColumn.d.ts.map +1 -0
- package/dist/utilities/LineAndColumn.js +52 -0
- package/dist/utilities/LineAndColumn.js.map +1 -0
- package/dist/utilities/Timer.d.ts +7 -3
- package/dist/utilities/Timer.d.ts.map +1 -1
- package/dist/utilities/Timer.js +41 -39
- package/dist/utilities/Timer.js.map +1 -1
- package/package.json +3 -3
- package/src/exports/Exports.ts +2 -0
- package/src/parser-generator/Grammar.ts +59 -265
- package/src/parser-generator/ParseError.ts +337 -0
- package/src/parser-generator/StaticAnalysis.ts +340 -0
- package/src/parser-generator/TerminalToText.ts +196 -0
- package/src/parser-generator/TopDownParser.ts +67 -49
- package/src/tests/Test.ts +26 -24
- package/src/tests/test-grammars/JsonGrammar.ts +28 -10
- package/src/tests/test-grammars/RegExpGrammar.ts +65 -46
- package/src/tests/test-grammars/XmlGrammar.ts +20 -8
- package/src/utilities/LineAndColumn.ts +61 -0
- package/src/utilities/Timer.ts +50 -48
|
@@ -5,7 +5,14 @@ export class RegExpGrammar {
|
|
|
5
5
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
|
6
6
|
// High-level productions
|
|
7
7
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
|
8
|
-
root = () => this.
|
|
8
|
+
root = () => this.disjunctionOrSequence
|
|
9
|
+
|
|
10
|
+
disjunctionOrSequence = () => [
|
|
11
|
+
G.anyOf(
|
|
12
|
+
this.disjunction,
|
|
13
|
+
this.sequence,
|
|
14
|
+
)
|
|
15
|
+
]
|
|
9
16
|
|
|
10
17
|
disjunction = () => [
|
|
11
18
|
this.sequence,
|
|
@@ -20,11 +27,27 @@ export class RegExpGrammar {
|
|
|
20
27
|
G.zeroOrMore(this.sequenceElement)
|
|
21
28
|
)
|
|
22
29
|
|
|
23
|
-
|
|
30
|
+
sequenceElement = () => G.anyOf(
|
|
31
|
+
this.anchor,
|
|
32
|
+
this.lookaround,
|
|
33
|
+
this.quantifiedExpression,
|
|
34
|
+
this.quantifiableExpression,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
//////////////////////////////////////////////////////////////////////////////////////////////
|
|
38
|
+
// Quantifiable expression
|
|
39
|
+
//////////////////////////////////////////////////////////////////////////////////////////////
|
|
40
|
+
quantifiableExpression = () => G.cached(
|
|
24
41
|
G.anyOf(
|
|
25
|
-
this.
|
|
26
|
-
this.
|
|
42
|
+
this.group,
|
|
43
|
+
this.backReference,
|
|
44
|
+
this.singleCharExpression
|
|
27
45
|
)
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
quantifiedExpression = () => [
|
|
49
|
+
this.quantifiableExpression,
|
|
50
|
+
this.quantifier,
|
|
28
51
|
]
|
|
29
52
|
|
|
30
53
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
|
@@ -86,7 +109,7 @@ export class RegExpGrammar {
|
|
|
86
109
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
|
87
110
|
uncapturedGroup = () => [
|
|
88
111
|
'(?:',
|
|
89
|
-
this.
|
|
112
|
+
this.disjunctionOrSequence,
|
|
90
113
|
')',
|
|
91
114
|
]
|
|
92
115
|
|
|
@@ -96,17 +119,17 @@ export class RegExpGrammar {
|
|
|
96
119
|
R.oneOrMore(identifierChar))
|
|
97
120
|
),
|
|
98
121
|
'>',
|
|
99
|
-
this.
|
|
122
|
+
this.disjunctionOrSequence,
|
|
100
123
|
')',
|
|
101
124
|
]
|
|
102
125
|
|
|
103
126
|
unnamedCaptureGroup = () => [
|
|
104
127
|
'(',
|
|
105
|
-
this.
|
|
128
|
+
this.disjunctionOrSequence,
|
|
106
129
|
')',
|
|
107
130
|
]
|
|
108
131
|
|
|
109
|
-
group = G.anyOf(
|
|
132
|
+
group = () => G.anyOf(
|
|
110
133
|
this.uncapturedGroup,
|
|
111
134
|
this.namedCaptureGroup,
|
|
112
135
|
this.unnamedCaptureGroup,
|
|
@@ -132,7 +155,7 @@ export class RegExpGrammar {
|
|
|
132
155
|
'>',
|
|
133
156
|
])
|
|
134
157
|
|
|
135
|
-
backReference = G.anyOf(
|
|
158
|
+
backReference = () => G.anyOf(
|
|
136
159
|
this.unnamedBackreference,
|
|
137
160
|
this.namedBackreference,
|
|
138
161
|
)
|
|
@@ -142,29 +165,29 @@ export class RegExpGrammar {
|
|
|
142
165
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
|
143
166
|
positiveLookahead = () => [
|
|
144
167
|
'(?=',
|
|
145
|
-
this.
|
|
168
|
+
this.disjunctionOrSequence,
|
|
146
169
|
')',
|
|
147
170
|
]
|
|
148
171
|
|
|
149
172
|
negativeLookahead = () => [
|
|
150
173
|
'(?!',
|
|
151
|
-
this.
|
|
174
|
+
this.disjunctionOrSequence,
|
|
152
175
|
')',
|
|
153
176
|
]
|
|
154
177
|
|
|
155
178
|
positiveLookbehind = () => [
|
|
156
179
|
'(?<=',
|
|
157
|
-
this.
|
|
180
|
+
this.disjunctionOrSequence,
|
|
158
181
|
')',
|
|
159
182
|
]
|
|
160
183
|
|
|
161
184
|
negativeLookbehind = () => [
|
|
162
185
|
'(?<!',
|
|
163
|
-
this.
|
|
186
|
+
this.disjunctionOrSequence,
|
|
164
187
|
')',
|
|
165
188
|
]
|
|
166
189
|
|
|
167
|
-
lookaround = G.anyOf(
|
|
190
|
+
lookaround = () => G.anyOf(
|
|
168
191
|
this.positiveLookahead,
|
|
169
192
|
this.negativeLookahead,
|
|
170
193
|
this.positiveLookbehind,
|
|
@@ -244,17 +267,17 @@ export class RegExpGrammar {
|
|
|
244
267
|
'}',
|
|
245
268
|
])
|
|
246
269
|
|
|
247
|
-
unicodeProperty = () =>
|
|
270
|
+
unicodeProperty = () => [
|
|
248
271
|
'\\p',
|
|
249
272
|
this.unicodePropertyBodyPattern,
|
|
250
|
-
]
|
|
273
|
+
]
|
|
251
274
|
|
|
252
|
-
notUnicodeProperty = () =>
|
|
275
|
+
notUnicodeProperty = () => [
|
|
253
276
|
'\\P',
|
|
254
277
|
this.unicodePropertyBodyPattern,
|
|
255
|
-
]
|
|
278
|
+
]
|
|
256
279
|
|
|
257
|
-
unicodePropertyBodyPattern
|
|
280
|
+
unicodePropertyBodyPattern = () => G.pattern([
|
|
258
281
|
'{',
|
|
259
282
|
R.captureAs('property',
|
|
260
283
|
R.oneOrMore(R.anyOf(letter, digit, '_', '-'))
|
|
@@ -266,7 +289,7 @@ export class RegExpGrammar {
|
|
|
266
289
|
),
|
|
267
290
|
]),
|
|
268
291
|
'}',
|
|
269
|
-
]
|
|
292
|
+
])
|
|
270
293
|
|
|
271
294
|
charClass = () => [
|
|
272
295
|
'[',
|
|
@@ -283,7 +306,7 @@ export class RegExpGrammar {
|
|
|
283
306
|
']',
|
|
284
307
|
]
|
|
285
308
|
|
|
286
|
-
charcodeOrEscapedChar = G.anyOf(
|
|
309
|
+
charcodeOrEscapedChar = () => G.anyOf(
|
|
287
310
|
this.codepoint,
|
|
288
311
|
this.hexCharcode,
|
|
289
312
|
this.controlCharcode,
|
|
@@ -297,7 +320,7 @@ export class RegExpGrammar {
|
|
|
297
320
|
)
|
|
298
321
|
)
|
|
299
322
|
|
|
300
|
-
charRangeElement = G.anyOf(
|
|
323
|
+
charRangeElement = () => G.anyOf(
|
|
301
324
|
this.charcodeOrEscapedChar,
|
|
302
325
|
this.charClassLiteral,
|
|
303
326
|
)
|
|
@@ -321,7 +344,8 @@ export class RegExpGrammar {
|
|
|
321
344
|
|
|
322
345
|
anyCharWildcard = () => '.'
|
|
323
346
|
|
|
324
|
-
anchor =
|
|
347
|
+
anchor = () =>
|
|
348
|
+
G.anyOf(this.inputStartAnchor, this.inputEndAnchor)
|
|
325
349
|
|
|
326
350
|
escapedCharacterClass = () => G.pattern(R.anyOf(
|
|
327
351
|
R.captureAs('whitespace', escapedChars.whitespace),
|
|
@@ -339,29 +363,6 @@ export class RegExpGrammar {
|
|
|
339
363
|
R.captureAs('verticalTab', escapedChars.verticalTab),
|
|
340
364
|
R.captureAs('backwardSlash', escapedChars.backwardSlash),
|
|
341
365
|
))
|
|
342
|
-
|
|
343
|
-
//////////////////////////////////////////////////////////////////////////////////////////////
|
|
344
|
-
// Sequence elements (positioned here due to TypeScript class member ordering requirements)
|
|
345
|
-
//////////////////////////////////////////////////////////////////////////////////////////////
|
|
346
|
-
quantifiableExpression = G.cached(
|
|
347
|
-
G.anyOf(
|
|
348
|
-
this.group,
|
|
349
|
-
this.backReference,
|
|
350
|
-
this.singleCharExpression
|
|
351
|
-
)
|
|
352
|
-
)
|
|
353
|
-
|
|
354
|
-
quantifiedExpression = () => [
|
|
355
|
-
this.quantifiableExpression,
|
|
356
|
-
this.quantifier,
|
|
357
|
-
]
|
|
358
|
-
|
|
359
|
-
sequenceElement = G.anyOf(
|
|
360
|
-
this.anchor,
|
|
361
|
-
this.lookaround,
|
|
362
|
-
this.quantifiedExpression,
|
|
363
|
-
this.quantifiableExpression,
|
|
364
|
-
)
|
|
365
366
|
}
|
|
366
367
|
|
|
367
368
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
|
@@ -391,3 +392,21 @@ const escapedChars = {
|
|
|
391
392
|
verticalTab: '\\v',
|
|
392
393
|
backwardSlash: '\\\\'
|
|
393
394
|
}
|
|
395
|
+
|
|
396
|
+
//////////////////////////////////////////////////////////////////////////////////////////////
|
|
397
|
+
// Wrapped nonterminal names
|
|
398
|
+
//////////////////////////////////////////////////////////////////////////////////////////////
|
|
399
|
+
export const regExpGrammarUnwrappedNonterminalNames: G.GrammarNonterminalNames<RegExpGrammar> = [
|
|
400
|
+
'disjunctionOrSequence',
|
|
401
|
+
'group',
|
|
402
|
+
'backReference',
|
|
403
|
+
'lookaround',
|
|
404
|
+
'unicodeProperty',
|
|
405
|
+
'notUnicodeProperty',
|
|
406
|
+
'unicodePropertyBodyPattern',
|
|
407
|
+
'charcodeOrEscapedChar',
|
|
408
|
+
'charRangeElement',
|
|
409
|
+
'anchor',
|
|
410
|
+
'quantifiableExpression',
|
|
411
|
+
'sequenceElement',
|
|
412
|
+
]
|
|
@@ -14,7 +14,7 @@ export class XmlGrammar {
|
|
|
14
14
|
)
|
|
15
15
|
]
|
|
16
16
|
|
|
17
|
-
textFragment = G.pattern([
|
|
17
|
+
textFragment = () => G.pattern([
|
|
18
18
|
R.oneOrMore(R.notAnyOfChars('<'))
|
|
19
19
|
])
|
|
20
20
|
|
|
@@ -26,7 +26,7 @@ export class XmlGrammar {
|
|
|
26
26
|
this.tagEnd
|
|
27
27
|
]
|
|
28
28
|
|
|
29
|
-
openingTagStart = G.pattern([
|
|
29
|
+
openingTagStart = () => G.pattern([
|
|
30
30
|
'<',
|
|
31
31
|
|
|
32
32
|
R.possibly('?'),
|
|
@@ -38,7 +38,7 @@ export class XmlGrammar {
|
|
|
38
38
|
R.zeroOrMore(R.whitespace),
|
|
39
39
|
])
|
|
40
40
|
|
|
41
|
-
tagEnd = G.pattern([
|
|
41
|
+
tagEnd = () => G.pattern([
|
|
42
42
|
R.zeroOrMore(R.whitespace),
|
|
43
43
|
|
|
44
44
|
R.possibly(R.anyOf('/', '?')),
|
|
@@ -46,7 +46,7 @@ export class XmlGrammar {
|
|
|
46
46
|
'>'
|
|
47
47
|
])
|
|
48
48
|
|
|
49
|
-
attribute = G.pattern([
|
|
49
|
+
attribute = () => G.pattern([
|
|
50
50
|
R.zeroOrMore(R.whitespace),
|
|
51
51
|
|
|
52
52
|
R.captureAs('attributeName',
|
|
@@ -66,7 +66,7 @@ export class XmlGrammar {
|
|
|
66
66
|
])
|
|
67
67
|
])
|
|
68
68
|
|
|
69
|
-
closingTag = G.pattern([
|
|
69
|
+
closingTag = () => G.pattern([
|
|
70
70
|
'</',
|
|
71
71
|
|
|
72
72
|
R.zeroOrMore(R.whitespace),
|
|
@@ -88,7 +88,7 @@ export class XmlGrammar {
|
|
|
88
88
|
this.tagEnd
|
|
89
89
|
]
|
|
90
90
|
|
|
91
|
-
declarationTagOpening = G.pattern([
|
|
91
|
+
declarationTagOpening = () => G.pattern([
|
|
92
92
|
'<!',
|
|
93
93
|
|
|
94
94
|
R.captureAs('tagName',
|
|
@@ -98,7 +98,7 @@ export class XmlGrammar {
|
|
|
98
98
|
R.zeroOrMore(R.whitespace)
|
|
99
99
|
])
|
|
100
100
|
|
|
101
|
-
declarationTagAttribute = G.pattern([
|
|
101
|
+
declarationTagAttribute = () => G.pattern([
|
|
102
102
|
R.zeroOrMore(R.whitespace),
|
|
103
103
|
|
|
104
104
|
R.anyOf(
|
|
@@ -112,7 +112,7 @@ export class XmlGrammar {
|
|
|
112
112
|
R.zeroOrMore(R.whitespace),
|
|
113
113
|
])
|
|
114
114
|
|
|
115
|
-
comment = G.pattern([
|
|
115
|
+
comment = () => G.pattern([
|
|
116
116
|
'<!--',
|
|
117
117
|
|
|
118
118
|
R.captureAs('commentBody',
|
|
@@ -123,6 +123,9 @@ export class XmlGrammar {
|
|
|
123
123
|
])
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
//////////////////////////////////////////////////////////////////////////////////////////////
|
|
127
|
+
// Helper regular expressions
|
|
128
|
+
//////////////////////////////////////////////////////////////////////////////////////////////
|
|
126
129
|
const quotedString = R.anyOf(
|
|
127
130
|
[
|
|
128
131
|
'"',
|
|
@@ -139,3 +142,12 @@ const quotedString = R.anyOf(
|
|
|
139
142
|
"'"
|
|
140
143
|
],
|
|
141
144
|
)
|
|
145
|
+
|
|
146
|
+
//////////////////////////////////////////////////////////////////////////////////////////////
|
|
147
|
+
// Wrapped nonterminal names
|
|
148
|
+
//////////////////////////////////////////////////////////////////////////////////////////////
|
|
149
|
+
export const xmlGrammarUnwrappedNonterminalNames: G.GrammarNonterminalNames<XmlGrammar> = [
|
|
150
|
+
'openingTagStart',
|
|
151
|
+
'tagEnd',
|
|
152
|
+
'declarationTagOpening',
|
|
153
|
+
]
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Position helpers
|
|
3
|
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
|
4
|
+
export function getLineAndColumn(input: string, offset: number): { line: number, column: number } {
|
|
5
|
+
let line = 1
|
|
6
|
+
let lineStartOffset = 0
|
|
7
|
+
|
|
8
|
+
for (let i = 0; i < offset && i < input.length; i++) {
|
|
9
|
+
if (input[i] === '\n') {
|
|
10
|
+
line += 1
|
|
11
|
+
lineStartOffset = i + 1
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
line,
|
|
17
|
+
column: offset - lineStartOffset + 1
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const tabSize = 4
|
|
22
|
+
|
|
23
|
+
export function expandTabs(text: string): string {
|
|
24
|
+
let result = ''
|
|
25
|
+
let column = 0
|
|
26
|
+
|
|
27
|
+
for (const char of text) {
|
|
28
|
+
if (char === '\t') {
|
|
29
|
+
const spacesToNextTabStop = tabSize - (column % tabSize)
|
|
30
|
+
|
|
31
|
+
result += ' '.repeat(spacesToNextTabStop)
|
|
32
|
+
column += spacesToNextTabStop
|
|
33
|
+
} else {
|
|
34
|
+
result += char
|
|
35
|
+
column += 1
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return result
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function buildCaretSpacing(text: string, caretColumn: number): string {
|
|
43
|
+
let spacing = ''
|
|
44
|
+
let column = 0
|
|
45
|
+
|
|
46
|
+
for (let i = 0; i < caretColumn - 1 && i < text.length; i++) {
|
|
47
|
+
const char = text[i]
|
|
48
|
+
|
|
49
|
+
if (char === '\t') {
|
|
50
|
+
const spacesToNextTabStop = tabSize - (column % tabSize)
|
|
51
|
+
|
|
52
|
+
spacing += ' '.repeat(spacesToNextTabStop)
|
|
53
|
+
column += spacesToNextTabStop
|
|
54
|
+
} else {
|
|
55
|
+
spacing += ' '
|
|
56
|
+
column += 1
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return spacing
|
|
61
|
+
}
|
package/src/utilities/Timer.ts
CHANGED
|
@@ -1,95 +1,97 @@
|
|
|
1
|
-
|
|
1
|
+
export class Timer {
|
|
2
|
+
private readonly logger: TimerLogger
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
declare const process: any
|
|
4
|
+
private startTime = 0
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
constructor(logger?: TimerLogger) {
|
|
7
|
+
if (logger) {
|
|
8
|
+
this.logger = logger
|
|
9
|
+
} else {
|
|
10
|
+
this.logger = console.log
|
|
11
|
+
}
|
|
8
12
|
|
|
9
|
-
constructor() {
|
|
10
13
|
this.restart()
|
|
11
14
|
}
|
|
12
15
|
|
|
13
|
-
|
|
16
|
+
// Resets the timer to the current time.
|
|
17
|
+
restart(): void {
|
|
14
18
|
this.startTime = Timer.currentTime
|
|
15
19
|
}
|
|
16
20
|
|
|
21
|
+
// Elapsed time in milliseconds (monotonic where supported).
|
|
17
22
|
get elapsedTime(): number {
|
|
18
|
-
// Elapsed time (milliseconds)
|
|
19
23
|
return Timer.currentTime - this.startTime
|
|
20
24
|
}
|
|
21
25
|
|
|
26
|
+
// Elapsed time in seconds.
|
|
22
27
|
get elapsedTimeSeconds(): number {
|
|
23
|
-
// Elapsed time (seconds)
|
|
24
28
|
return this.elapsedTime / 1000
|
|
25
29
|
}
|
|
26
30
|
|
|
31
|
+
// Returns elapsed ms and restarts the timer.
|
|
27
32
|
getElapsedTimeAndRestart(): number {
|
|
28
|
-
const
|
|
33
|
+
const elapsed = this.elapsedTime
|
|
29
34
|
this.restart()
|
|
30
35
|
|
|
31
|
-
return
|
|
36
|
+
return elapsed
|
|
32
37
|
}
|
|
33
38
|
|
|
39
|
+
// Logs elapsed time (in ms) and restarts the timer.
|
|
34
40
|
logAndRestart(title: string, timePrecision = 3): number {
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
//
|
|
38
|
-
const message = `${title}: ${roundToDigits(elapsedTime, timePrecision)}ms`
|
|
39
|
-
|
|
40
|
-
console.log(message)
|
|
41
|
-
//
|
|
42
|
-
|
|
41
|
+
const elapsedMs = this.elapsedTime
|
|
42
|
+
this.logger(`${title}: ${roundToDigits(elapsedMs, timePrecision)}ms`)
|
|
43
43
|
this.restart()
|
|
44
44
|
|
|
45
|
-
return
|
|
45
|
+
return elapsedMs
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
// Current high-resolution timestamp in milliseconds since Unix epoch.
|
|
48
49
|
static get currentTime(): number {
|
|
49
|
-
if (!this.timestampFunc) {
|
|
50
|
-
this.createGlobalTimestampFunction()
|
|
51
|
-
}
|
|
52
|
-
|
|
53
50
|
return this.timestampFunc()
|
|
54
51
|
}
|
|
55
52
|
|
|
53
|
+
// Current timestamp in microseconds (integer).
|
|
56
54
|
static get microsecondTimestamp(): number {
|
|
57
55
|
return Math.floor(Timer.currentTime * 1000)
|
|
58
56
|
}
|
|
59
57
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
let baseTimestamp = 0
|
|
58
|
+
// Private Static Clock Setup
|
|
59
|
+
private static timestampFunc: () => number = Timer.createTimestampFunction()
|
|
63
60
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const millisecondTime = (nodeTimeStamp[0] * 1000) + (nodeTimeStamp[1] / 1000000)
|
|
61
|
+
private static createTimestampFunction(): () => number {
|
|
62
|
+
const g = globalThis as any
|
|
67
63
|
|
|
68
|
-
|
|
69
|
-
|
|
64
|
+
// 1. Modern standard: performance.now() (Browsers & Node 16+)
|
|
65
|
+
if (typeof g.performance === 'object' && typeof g.performance.now === 'function') {
|
|
66
|
+
const timeOrigin =
|
|
67
|
+
g.performance.timeOrigin ?? (Date.now() - g.performance.now())
|
|
70
68
|
|
|
71
|
-
|
|
69
|
+
return () => timeOrigin + g.performance.now()
|
|
72
70
|
}
|
|
73
|
-
else if (typeof chrome === 'object' && chrome.Interval) {
|
|
74
|
-
const baseTimestamp = Date.now()
|
|
75
71
|
|
|
76
|
-
|
|
77
|
-
|
|
72
|
+
// 2. Node.js high resolution timer (BigInt variant, Node 10.4+)
|
|
73
|
+
if (typeof g.process === 'object' && typeof g.process.hrtime === 'function') {
|
|
74
|
+
const startNs = g.process.hrtime.bigint()
|
|
78
75
|
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
else if (typeof performance === 'object' && performance.now) {
|
|
82
|
-
const baseTimestamp = Date.now() - performance.now()
|
|
76
|
+
const epochBaseMs = Date.now() - (Number(startNs) / 1e6)
|
|
83
77
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
else if (Date.now) {
|
|
87
|
-
this.timestampFunc = () => Date.now()
|
|
78
|
+
return () =>
|
|
79
|
+
epochBaseMs + Number(g.process.hrtime.bigint()) / 1e6
|
|
88
80
|
}
|
|
89
|
-
|
|
90
|
-
|
|
81
|
+
|
|
82
|
+
// 3. Last-resort fallback (non-monotonic)
|
|
83
|
+
if (typeof Date.now === 'function') {
|
|
84
|
+
return () => Date.now()
|
|
91
85
|
}
|
|
86
|
+
|
|
87
|
+
return () => new Date().getTime()
|
|
92
88
|
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function roundToDigits(value: number, digits: number): number {
|
|
92
|
+
const factor = 10 ** digits
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
return Math.round(value * factor) / factor
|
|
95
95
|
}
|
|
96
|
+
|
|
97
|
+
type TimerLogger = (msg: string) => void
|