Skip to content

Commit

Permalink
dev: token/caret error message borders
Browse files Browse the repository at this point in the history
  • Loading branch information
blake-regalia committed Apr 24, 2021
1 parent cd211a3 commit 28dda23
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 47 deletions.
109 changes: 80 additions & 29 deletions src/content/_t/parse.js.jmacs
Original file line number Diff line number Diff line change
Expand Up @@ -278,19 +278,39 @@ const OPHOP = Object.prototype.hasOwnProperty;
@;


@def error_args()
@def error_args(gc_args={})
@//@object-literal
@if gc_args.rewind
...(() => {
const i_token_end = this.i - /[ \t]*$/.exec(this.s.slice(0, this.i))[0].length;
const i_token_start = i_token_end - (@{gc_args.rewind});
const i_caret = i_token_start + (@{gc_args.caret});

return {
@;
content: this,
source: this.s,
position: this.i,
@if gc_args.rewind
token: {
caret: i_caret,
start: i_token_start,
end: i_token_end,
},
@else
token: {caret:this.i},
@;
...this._b_line_tracking
? {
location: {
line: this._c_lines+count_lines(this.s, this.i),
col: this.i - this.s.lastIndexOf('\n', this.i),
line: 1 + this._c_lines + count_lines_until(this.s, this.i)@{gc_args.line || ''},
col: @{gc_args.rewind? /* syntax: js */ `i_caret`: /* syntax: js */ `this.i`} - this.s.lastIndexOf('\n', this.i),
},
}
: {},

@if gc_args.rewind
}})(),
@;
@;


Expand Down Expand Up @@ -1406,23 +1426,25 @@ const match_prefixed_name = (s, i) => {
return this.after_eot;
@;

function count_lines(s_chunk, i_until=Infinity) {

function count_all_lines(s_chunk) {
let c_lines = 0;
let i_scan = 0;

if(Number.isFinite(i_until)) {
for(;;) {
let i_next = s_chunk.indexOf('\n', i_scan);
if(i_next < 0 || i_next >= i_until) return c_lines;
c_lines += 1;
i_scan = i_next + 1;
}
let i_scan = -1;

while((i_scan = s_chunk.indexOf('\n', i_scan+1)) >= 0) {
c_lines += 1;
}
else {
while((i_scan = s_chunk.indexOf('\n', i_scan)) >= 0) {
c_lines += 1;
}
return c_lines;
return c_lines;
}

function count_lines_until(s_chunk, i_until=0) {
let c_lines = 0;
let i_scan = -1;

for(;;) {
i_scan = s_chunk.indexOf('\n', i_scan+1);
if(i_scan < 0 || i_scan >= i_until) return c_lines;
c_lines += 1;
}
}

Expand Down Expand Up @@ -1451,7 +1473,7 @@ function count_lines(s_chunk, i_until=Infinity) {
export class @{S_EXPORT} extends stream.@{B_LOAD? /* syntax: js */ `Writable`: /* syntax: js */ `Transform`} {
static async run(z_input, gc_run) {
// instantiate consumer
const k_consumer = new Consumer(gc_run);
const k_consumer = new Consumer(gc_run, @{S_EXPORT});

// string
if('string' === typeof z_input) {
Expand Down Expand Up @@ -1481,7 +1503,7 @@ export class @{S_EXPORT} extends stream.@{B_LOAD? /* syntax: js */ `Writable`: /
this._h_rewired = {};

// instantiate consumer
const k_consumer = this._k_consumer = new Consumer(gc_consumer); // eslint-disable-line no-new
const k_consumer = this._k_consumer = new Consumer(gc_consumer, @{S_EXPORT}); // eslint-disable-line no-new

// proxy stream
k_consumer.proxy_transform(this);
Expand Down Expand Up @@ -1584,16 +1606,20 @@ export default @{S_EXPORT};


const F_NOOP = () => {};
const F_THROW = (e_what) => {throw e_what};
const F_THROW = (e_what) => {
throw e_what;
};

class Consumer {
constructor(gc_consumer) {
constructor(gc_consumer, dc_actor) {
// impl-specific configs
let {
// a state to inherit
state: g_state={},
} = this._gc_consumer = gc_consumer;

this._dc_actor = dc_actor;

// assign event callbacks
@*{
for(const [si_event, g_event] of ode(H_PARSE_EVENTS)) {
Expand Down Expand Up @@ -1883,7 +1909,17 @@ class Consumer {
if(!RT_BLANK_NODE_VALID.test(s_label)) {
throw new ContentSyntaxError({
message: \`Invalid blank node label: '\${s_label}'\`,
${error_args()}
${error_args({
// past length of label and `_:` sequence
rewind: /* syntax: js */ `s_label.length + 2`,

// position the caret at: [0] first label char [1] next extra-range [2] last char
caret: /* syntax: js */ `2 + (
/[^${RANGE_PN_CHARS_U()}0-9]/u.test(s_label[0])
? 0
: /[^${RANGE_PN_CHARS_U()}\\-0-9\\xb7]|[^\\u{0300}-\\u{036f}\\u{203f}-\\u{2040}]/u.exec(s_label.slice(1)).index || s_label.length-1
)`.replace(/[\r\n\t]+/g, ''),
})}
});
}

Expand All @@ -1898,7 +1934,13 @@ class Consumer {
if(!RT_NAMED_NODE_VALID.test(p_iri)) {
throw new ContentSyntaxError({
message: \`Invalid IRI: <\${p_iri}>\`,
${error_args()}
${error_args({
// past length of iri and `<`, `>`` chars
rewind: /* syntax: js */ `p_iri.length + 2`,

// TODO: find invalid char
caret: `1`,
})}
});
}
return namedNode(p_iri);
Expand All @@ -1908,7 +1950,11 @@ class Consumer {
if(!RT_NAMED_NODE_ESCAPELESS_VALID.test(p_iri)) {
throw new ContentSyntaxError({
message: \`Invalid IRI: <\${p_iri}>\`,
${error_args()}
${error_args({
// past `>` char, length of iri, then back to index of first invalid char
rewind: /* syntax: js */ `p_iri.length + 2`,
caret: /* syntax: js */ `1 + /[${RANGE_NAMED_NODE_INVALID(true)}]/.exec(p_iri).index`,
})}
});
}
return namedNode(p_iri);
Expand Down Expand Up @@ -2024,7 +2070,7 @@ class Consumer {

// count lines
if(this._b_line_tracking) {
this._c_lines += count_lines(s_chunk, -1);
this._c_lines += count_all_lines(s_chunk);
}

@if b_fke_chunk
Expand Down Expand Up @@ -2282,11 +2328,14 @@ class Consumer {
if(null === this.s) return;
// still invalid parsing state
if(this.@{S_STATE_PRIMARY} !== this._f_state) {
debugger;
// handle using callback
return new UnexpectedTokenError({
state: this._f_state.name,
found: '<<EOF>>',
@{error_args()}
@{error_args({
line: /* syntax: js */ `+ count_all_lines(this.s)`,
})}
});
}
}
Expand Down Expand Up @@ -2314,7 +2363,9 @@ class Consumer {
// bad input; parse error
return new UnexpectedTokenError({
state: this._f_state.name,
@{error_args()}
@{error_args({
line: /* syntax: js */ `+ count_all_lines(this.s)`,
})}
});
}
}
Expand Down
79 changes: 62 additions & 17 deletions src/content/error.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,23 @@ export class ContentError extends Error {
this._s_message = gc_error.message;
this._s_state = gc_error.state;
this._s_source = gc_error.source;
this._i_position = gc_error.position;
this._g_location = gc_error.location;

this.name = `@graphy/content.${this.name}`;
({
caret: this._i_caret,
start: this._i_token_start=-1,
end: this._i_token_end=-1,
} = gc_error.token);

this.name = `@graphy/content.${this.title}`;
// this.name = this.title;
}

get title() {
return (this.name || this.constructor.name)+'<'+this.instanceName+'> : '+this.description;
return (this.name || this.constructor.name)+'<'+this.instanceName+'>';
}

get instanceName() {
return this._k_content.constructor.name;
return this._k_content._dc_actor.name;
}

get message() {
Expand All @@ -28,19 +33,59 @@ export class ContentError extends Error {
// }

toString() {
const i_pos = this._i_position;

// 55 sets the relative bias to the front of the message
const i_off = Math.min(i_pos, Math.abs(i_pos-55));

// 90 sets the max width of the preview
const s_preview = this._s_source.substr(i_off, 90).replace(/[\n\t]/g, ' ');
const i_pos = this._i_caret;

// sets the max width of the preview
const nl_width = Math.min(90, process?.stdout?.columns || 90);

// 0.62 sets the relative bias to the front of the message
const i_off = Math.min(i_pos, Math.abs(i_pos-Math.floor(nl_width*0.62)));

// format preview
const s_preview = this._s_source.substr(i_off, nl_width).replace(/[\n\t]/g, ' ');
debugger;
// border strings
const nl_pre = i_pos - i_off;
let s_border_top= '';
let s_border_btm = '';

if(this._i_token_start >= 0) {
const a_border_top = '┈'.repeat(nl_width).split('');
const a_border_btm = [...a_border_top];

const ir_token_start = this._i_token_start - i_off;
const ir_caret = this._i_caret - i_off;
const ir_token_end = this._i_token_end - i_off;

if(ir_token_start >= 0) {
a_border_top[ir_token_start] = '┍';
a_border_btm[ir_token_start] = '┕';
}

if(ir_token_end < nl_width) {
a_border_top[ir_token_end-1] = '┑';
a_border_btm[ir_token_end-1] = '┙';
}

a_border_top[ir_caret] = '┻';
a_border_btm[ir_caret] = '┳';

s_border_top = a_border_top.join('');
s_border_btm = a_border_btm.join('');
}
else {
s_border_top = '┈'.repeat(nl_pre);
const s_post = '┈'.repeat(nl_width-nl_pre-1);
s_border_btm = s_border_top+'┳'+s_post;
s_border_top += '┻'+s_post;
}

return this.description+'\n'
+(this._g_location? ` at { line: ${this._g_location.line}, col: ${this._g_location.col} }\n`: ' to see the line/col offset, remove or disable the `swift: true` option')
+`\n ${s_preview}\n`
+` ${' '.repeat(i_pos-i_off)}^\n`
+` ${this._s_message}`;
+(this._g_location? ` at { line: ${this._g_location.line}, col: ${this._g_location.col} }`: ' to see the line/col offset, remove or disable the `swift: true` option')+'\n'
+` ${s_border_top}\n`
+` ${s_preview}\n`
+` ${s_border_btm}\n`
+`\n ${this._s_message}`;
}
}

Expand All @@ -51,7 +96,7 @@ ContentSyntaxError.prototype.description = 'A syntax error was found while readi
export class UnexpectedTokenError extends ContentSyntaxError {
constructor(gc_error) {
super(gc_error);
const s_char = this._s_source[this._i_position];
const s_char = this._s_source[this._i_caret];
this._s_message = `Expected ${this._s_state} ${gc_error.eofed? 'but encountered <<EOF>>': ''}. Failed to parse a valid token starting at ${s_char? '"'+s_char+'"': '<<EOF>>'}`;
}
}
Expand Down
7 changes: 6 additions & 1 deletion src/content/text.read.jmacs
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,14 @@
\\u[@{s_ins}A-Fa-f0-9]{4}|\\U[@{s_ins}A-Fa-f0-9]{8}
@;

@>> RANGE_NAMED_NODE_INVALID(b_escapeless)
@//@regex
\0-\x20<>"{}|^`@{b_escapeless? '': /\\/.source}
@;

@>> RT_NAMED_NODE_VALID(b_escapeless=false)
@//@regex
^([^\0-\x20<>"{}|^`@{b_escapeless? ']': /\\/.source+']|'+UCHAR()})*$ @// need to test for `>` since it can be encoded as unicode escape sequence
^([^@{RANGE_NAMED_NODE_INVALID(b_escapeless)}]@{b_escapeless? '': '|'+UCHAR()})*$ @// need to test for `>` since it can be encoded as unicode escape sequence
@;


Expand Down

0 comments on commit 28dda23

Please sign in to comment.