Skip to content

Commit

Permalink
dev: content syntax error previews
Browse files Browse the repository at this point in the history
  • Loading branch information
blake-regalia committed Apr 25, 2021
1 parent 28dda23 commit 33bc6b7
Showing 2 changed files with 139 additions and 70 deletions.
86 changes: 66 additions & 20 deletions src/content/_t/parse.js.jmacs
Original file line number Diff line number Diff line change
@@ -280,30 +280,47 @@ const OPHOP = Object.prototype.hasOwnProperty;

@def error_args(gc_args={})
@//@object-literal
@.{
let sj_index = /* syntax: js */ `this.i`;
}
@if gc_args.rewind
@.{
sj_index = /* syntax: js */ `i_caret`;
}
...(() => {
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});
@*{
let a_rewinds = gc_args.rewind;
if(!Array.isArray(a_rewinds)) a_rewinds = [a_rewinds];

let sj_token_prev = /* syntax: js */ `this.i`;
const nl_rewinds = a_rewinds.length;
for(let i_rewind=0; i_rewind<nl_rewinds; i_rewind++) {
const sj_token_curr = /* syntax: js */ `i_token_${nl_rewinds-i_rewind-1}`;
yield /* syntax: js */ `
const ${sj_token_curr}_end = ${sj_token_prev} - /[ \\t]*$/.exec(this.s.slice(0, ${sj_token_prev}))[0].length;`;
sj_token_prev = /* syntax: js */ `${sj_token_curr}_start`;
yield /* syntax: js */ `
const ${sj_token_prev} = ${sj_token_curr}_end - (${a_rewinds[i_rewind]});`;
}
}
const i_caret = i_token_0_start + (@{gc_args.caret});

return {
@;
content: this,
source: this.s,
@if gc_args.rewind
token: {
caret: i_caret,
start: i_token_start,
end: i_token_end,
},
@else
token: {caret:this.i},
@;
token: {
caret: @{sj_index},
@if gc_args.rewind
start: i_token_0_start,
end: i_token_0_end,
@;
},
...this._b_line_tracking
? {
location: {
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),
line: 1 + this._c_lines + count_lines_until(this.s, @{sj_index})@{gc_args.line || ''},
col: @{sj_index} - this.s.lastIndexOf('\n', @{sj_index}),
},
}
: {},
@@ -347,7 +364,17 @@ const OPHOP = Object.prototype.hasOwnProperty;
if(!b_tolerant && !RT_PREFIXED_NAME_NAMESPACE_VALID.test(s_prefix_id)) {
throw new ContentSyntaxError({
message: `Invalid namespace for prefixed name: '${s_prefix_id}:'`,
@{error_args()}
@{error_args({
rewind: [
/* syntax: js */ `p_prefix_iri.length + 2`,
/* syntax: js */ `s_prefix_id.length + 1`,
],
caret: /* syntax: js */ `
/[^${RANGE_PN_CHARS_BASE()}]/u.test(s_prefix_id[0])
? 0
: /[^.${RANGE_PN_CHARS_U()}\\-0-9\\xb7\\u{203f}-\\u{2040}]/u.exec(s_prefix_id)?.index || s_prefix_id.length-1
`,
})}
});
}

@@ -1606,8 +1633,8 @@ export default @{S_EXPORT};


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

class Consumer {
@@ -1883,7 +1910,7 @@ class Consumer {
@;

// term constructors
if(this.tolerant) {
if(this._b_tolerant) {
@*{yield* declare_properties({
// term constructors
blank_node: /* syntax: js */ `function(s_label) {
@@ -1917,7 +1944,7 @@ class Consumer {
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
: /[^${RANGE_PN_CHARS_U()}\\-0-9\\xb7\\u{203f}-\\u{2040}]/u.exec(s_label)?.index || s_label.length-1
)`.replace(/[\r\n\t]+/g, ''),
})}
});
@@ -2095,6 +2122,25 @@ class Consumer {
consume_stream(ds_input) {
// go async
return new Promise((fk_consumed, fe_reject) => {
// re-route error handling to promise rejection
if(F_THROW === this._fe_error) {
this._fe_error = fe_reject;
}
// wrap error handler
else {
// ref original handler
const fe_handler = this._fe_error;

// set new handler
this._fe_error = (e_read) => {
// callback user-defined error handler
fe_handler(e_read);

// then, reject promise
fe_reject(e_read);
};
}

// bind data listener
ds_input.on('data', (s_chunk) => {
@{write_chunk()}
@@ -2109,7 +2155,7 @@ class Consumer {
const e_eof = this.eof(1);

// reject with failure
if(e_eof) return fe_reject(e_eof);
if(e_eof) return this._fe_error(e_eof);

// resolve promise
fk_consumed();
123 changes: 73 additions & 50 deletions src/content/error.mjs
Original file line number Diff line number Diff line change
@@ -1,61 +1,37 @@
const HM_PRIVATES = new Map();

export class ContentError extends Error {
constructor(gc_error) {
super();
this._k_content = gc_error.content;
this._s_message = gc_error.message;
this._s_state = gc_error.state;
this._s_source = gc_error.source;
this._g_location = gc_error.location;
({
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+'>';
}

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

get message() {
return this.toString();
}

// get stack() {
// return this.title+'::'+this._s_message+'\n'+this.toString();
// }

toString() {
const i_pos = this._i_caret;
const {
caret: i_caret,
start: i_token_start=-1,
end: i_token_end=-1,
} = gc_error.token;

// 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)));
const i_off = Math.min(i_caret, Math.abs(i_caret-Math.floor(nl_width*0.62)));

// format preview
const s_preview = this._s_source.substr(i_off, nl_width).replace(/[\n\t]/g, ' ');
debugger;
const s_preview = gc_error.source.substr(i_off, nl_width).replace(/[\n\t]/g, ' ');

// border strings
const nl_pre = i_pos - i_off;
const nl_pre = i_caret - i_off;
let s_border_top= '';
let s_border_btm = '';

if(this._i_token_start >= 0) {
// token highlighting
if(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;
const ir_token_start = i_token_start - i_off;
const ir_caret = i_caret - i_off;
const ir_token_end = i_token_end - i_off;

if(ir_token_start >= 0) {
a_border_top[ir_token_start] = '┍';
@@ -67,37 +43,82 @@ debugger;
a_border_btm[ir_token_end-1] = '┙';
}

a_border_top[ir_caret] = '┻';
a_border_btm[ir_caret] = '┳';
if(ir_token_start !== ir_token_end && (ir_token_start === ir_caret || ir_token_end === ir_caret)) {
a_border_top[ir_caret] = '╇';
a_border_btm[ir_caret] = '╈';
}
else {
a_border_top[ir_caret] = '┻';
a_border_btm[ir_caret] = '┳';
}

s_border_top = a_border_top.join('');
s_border_btm = a_border_btm.join('');
}
// caret only
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;
}

const g_self = {
s_instance: gc_error.content?._dc_actor?.name,
s_message: gc_error.message,
s_state: gc_error.state,
s_source: gc_error.source,
g_location: gc_error.location,
i_caret,
s_preview,
s_border_top,
s_border_btm,
};

HM_PRIVATES.set(this, g_self);
}

get name() {
const {
s_instance,
s_name,
} = HM_PRIVATES.get(this);

const s_title = (s_name || this.constructor.name)+(s_instance? '<'+s_instance+'>': '');
return `@graphy/content.${s_title}`;
}

get message() {
const {
g_location,
s_border_top,
s_preview,
s_border_btm,
s_message,
} = HM_PRIVATES.get(this);

return this.description+'\n'
+(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'
+(g_location? ` at { line: ${g_location.line}, col: ${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}`;
+`\n ${s_message}`;
}

// get stack() {
// return this.title+'::'+this.message;
// }
}

export class ContentSyntaxError extends ContentError {}
ContentSyntaxError.prototype.name = 'ContentSyntaxError';
ContentSyntaxError.prototype.description = 'A syntax error was found while reading input.';
ContentSyntaxError.prototype.description = 'Syntax error found while reading input.';

export class UnexpectedTokenError extends ContentSyntaxError {
constructor(gc_error) {
super(gc_error);
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>>'}`;
const g_self = HM_PRIVATES.get(this);
const s_char = g_self.s_source[g_self.i_caret];
g_self.s_message = `Expected ${gc_error.state} ${gc_error.eofed? 'but encountered <<EOF>>': ''}. Failed to parse a valid token starting at ${s_char? '"'+s_char+'"': '<<EOF>>'}`;
}
}

@@ -107,15 +128,17 @@ InvalidStateChangeError.prototype.description = 'An invalid state change was det
export class NoSuchPrefixError extends ContentError {
constructor(gc_error) {
super(gc_error);
this._s_message = `No such prefix '${gc_error.prefix}' was declared.`;
const g_self = HM_PRIVATES.get(this);
g_self.s_message = `No such prefix '${gc_error.prefix}' was declared.`;
}
}
NoSuchPrefixError.description = 'Missing prefix declaration.';

export class ExceededMaximumTokenLengthError extends ContentError {
constructor(gc_error) {
super(gc_error);
this._s_message = `The maximum token length is currently set to ${gc_error.mtl}. You can adjust this value in the parameters.`;
const g_self = HM_PRIVATES.get(this);
g_self.s_message = `The maximum token length is currently set to ${gc_error.mtl}. You can adjust this value in the parameters.`;
}
}
ExceededMaximumTokenLengthError.prototype.description = 'Exceeded maximum token length while reading input.';

0 comments on commit 33bc6b7

Please sign in to comment.