Skip to content

Commit

Permalink
Implement logging for debugging / diagnostics
Browse files Browse the repository at this point in the history
  • Loading branch information
ivank committed Dec 30, 2022
1 parent aca2796 commit fc51ead
Show file tree
Hide file tree
Showing 5 changed files with 617 additions and 8 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ikerin/rd-parse",
"version": "4.0.1",
"version": "4.1.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"description": "Recursive descent parser generator. Define your grammar in pure Javascript.",
Expand Down
50 changes: 43 additions & 7 deletions src/parser.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ParserError } from './ParserError';
import { Context, Stack, FunctionRule, Rule, Token } from './types';
import { Context, Stack, FunctionRule, Rule, Token, Trace } from './types';

function locAt(text: string, newPos: number, { pos, line, column }: Context): Context {
while (pos < newPos) {
Expand All @@ -18,9 +18,37 @@ function markSeen($: Stack): void {
}
}

function logRule($: Stack, rule: string | RegExp): void {
if ($.log) {
const label = String(rule);
$.log.ruleCalls[label] = label in $.log.ruleCalls ? $.log.ruleCalls[label] + 1 : 1;
}
}

function logNode($: Stack): [number, number];
function logNode($: Stack, start: [number, number], node: Token): void;
function logNode($: Stack, start?: [number, number], node?: Token): void | [number, number] {
if ($.log) {
const time = process.hrtime(start);
if (start) {
const name = $.log.nodeTrace(node);
if (name) {
$.log.nodeExecutionTime[name] =
name in $.log.nodeExecutionTime
? [$.log.nodeExecutionTime[name][0] + time[0], $.log.nodeExecutionTime[name][1] + time[1]]
: time;
$.log.nodeCalls[name] = name in $.log.nodeCalls ? $.log.nodeCalls[name] + 1 : 1;
}
} else {
return time;
}
}
}

export function RegexToken(pattern: RegExp): FunctionRule {
return ($) => {
markSeen($);
logRule($, pattern);

const match = pattern.exec($.text.substring($.pos));
if (!match) return $;
Expand All @@ -39,6 +67,7 @@ export function RegexToken(pattern: RegExp): FunctionRule {
export function StringToken(pattern: string): FunctionRule {
return ($) => {
markSeen($);
logRule($, pattern);
return $.text.startsWith(pattern, $.pos) ? { ...$, pos: $.pos + pattern.length } : $;
};
}
Expand Down Expand Up @@ -187,14 +216,19 @@ export function Node<T extends Token = Token, Capture extends Token[] = Token[]>
const functionRule = Use(rule);

return ($) => {
const time = logNode($);
const $next = functionRule($);

if ($next === $) return $;

// We have a match
const node = reducer($.stack.slice($.sp, $next.sp) as Capture, $, $next);
$next.sp = $.sp;
if (node !== null) $.stack[$next.sp++] = node;

$next.sp = $.sp;
if (node !== null) {
$.stack[$next.sp++] = node;
logNode($, time, node);
}
return $next;
};
}
Expand Down Expand Up @@ -239,30 +273,32 @@ export function Y(proc: (x: FunctionRule) => FunctionRule): FunctionRule {
return ((x) => proc((y) => x(x)(y)))((x: any) => proc((y) => x(x)(y)));
}

const START = (text: string, pos = 0): Stack => ({
const START = (text: string, pos = 0, log?: (node: Token) => string): Stack => ({
text,
ignore: [],
stack: [],
comments: [],
sp: 0,
lastSeen: locAt(text, pos, { pos: 0, line: 1, column: 1 }),
pos,
...(log ? { log: { nodeTrace: log, nodeExecutionTime: {}, ruleCalls: {}, nodeCalls: {} } } : {}),
});

export function Parser<TAstToken extends Token = Token, TCommentToken extends Token = Token>(
Grammar: FunctionRule,
pos = 0,
partial = false,
log?: (node: Token) => string,
) {
return (text: string): { ast: TAstToken; comments: TCommentToken[] } => {
const $ = START(text, pos);
return (text: string): { ast: TAstToken; comments: TCommentToken[]; log?: Trace } => {
const $ = START(text, pos, log);
const $next = Grammar($);

if ($ === $next || (!partial && $next.pos < text.length)) {
// No match or haven't consumed the whole input
throw new ParserError(`Unexpected token at ${$.lastSeen.line}:${$.lastSeen.column}`, $);
}

return { ast: $.stack[0], comments: $next.comments };
return { ast: $.stack[0], comments: $next.comments, ...($.log ? { log: $.log } : {}) };
};
}
8 changes: 8 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ export interface Context {
column: number;
}

export interface Trace {
nodeTrace: (node: Token) => string;
ruleCalls: Record<string, number>;
nodeCalls: Record<string, number>;
nodeExecutionTime: Record<string, [number, number]>;
}

export interface Stack {
/**
* Current position within 'text'
Expand Down Expand Up @@ -35,6 +42,7 @@ export interface Stack {
*/
stack: Token[];
comments: Token[];
log?: Trace;
}

export type FunctionRule = ($: Stack) => Stack;
Expand Down
Loading

0 comments on commit fc51ead

Please sign in to comment.