Our team is using WebGL and Javascript for this jam, and to that end I am writing an improved version of the custom engine we used last time. The spirit of LudumDare is about collaboration and sharing, and in that spirit, we invite you to use our code!
First off, here is some code to pretty-print the content of gl.getShaderInfoLog. Why's it so great to pretty-print compilation errors? Well, the default messages I get from the browser are bare oneliners like ERROR: 0:10: 'x' : syntax error. Last jam, I had to hunt through the line numbers manually, which was inconvenient when the shader source was burred inside a script tag in an HTML file. Having learned that this was a problem, today I wrote a simple regex parser that prints much more helpful messages, like this:
```
When compiling vertex shader "a":
gl_Position = vec4(vertex,0.0,1.0);
world_coord = (inverse_view * vec3(vertex,1.0)).xy ◀◀◀ MISSING SOMETHING?
daytime = dot(world_coord,solar_vector);
▀▀▀▀▀▀▀
ERROR: 0:10: 'daytime' : syntax error```
It's even so nice as to try and find the line where the semicolon is missing. (Heuristically of course, I am not parsing GLSL.)
If you think that's cool, well, hopefully your driver/browser/whatever produces raw errors that textually look like my computer's. If so, then the following code may help you:
```javascript
// WebGL shader compilation errors don't provide a lot of context.
// This pretty-printer extracts line numbers from the message, and
// formats a helpful report on the site of the issue.
// My driver can return several errors on several lines, so first let's split them.
function prettyPrintShaderErrors(name,source,message) {
const errors = message.split(/
?
/);
const readouts = [];
for (const error of errors) {
if (error.length == 0) continue;
readouts.push(prettyPrintShaderError(name,source,error));
}
return readouts.join("
");
}
// Pretty-print a single error.
function prettyPrintShaderError(name,source,error) {
const lowEffortMessage = When compiling ${name}: ${error};
const lines = source.split(/
?
/);
// An OpenGL compilation error will look like:
// "ERROR: 0:11: 'daytime' : syntax error"
// So, the first thing we do is split at the :
const errorParts = error.split(":");
if (errorParts[0] !== "ERROR" || errorParts.length < 3) {
// Give up if it doesn't look like we're expecting.
return lowEffortMessage;
}
const [part,line] = [
parseInt(errorParts[1],10),
parseInt(errorParts[2],10) - 1 // OpenGL starts at line 1
];
if (part !== 0) return lowEffortMessage; // 'part' is an OpenGL thing that webGL shouldn't have. If it isn't zero, then we aren't properly parsing the error.
if (line >= lines.length) return lowEffortMessage;
// Attempt to find the error-triggering string in the bad line
// Strip whitespace and wrapping quotes
const triggering = errorParts[3].replace(/^\s+['|"]|['|"]\s+$/g, '');
const triggeringindex = lines[line].search(triggering);
// Probe for missing semicolons, a common error.
// This regex-based heuristic is NOT PERFECT, but it can work sometimes.
let semicolonmissingat = null;
// Semicolons, { and } can all go before a statement.
const goodLine = /[;|{|}]\s*(\/\/.*)?$/;
const emptyLine = /^\s*(\/\/.*)?$/;
for (let i=line-1; i>=0; --i) {
if (goodLine.test(lines[i])) break; // We found line that terminates right.
if (!emptyLine.test(lines[i])) { // If the line has stuff on it...
semicolonmissingat = i; // then since we haven't found a good one...
break; // it must be a bad one. We're done!
}
}
// Decide whether or not we suspect a missing semicolon/brace
let suspectedmissingsemicolon = false;
if (semicolonmissingat !== null && triggeringindex >= 0) {
// If the triggering string appears after nothing but whitespace
if (/\s*/.test(lines[line].slice(0,triggeringindex))) {
suspectedmissingsemicolon = true;
}
}
// Select context for error from source lines
const contextend = line+1; // Our context must include the triggering line!
const contextstart = contextend - 3; // 3 lines of context by default
if (semicolonmissingat !== null && contextstart > semicolonmissingat) {
contextstart = semicolonmissingat; // Always include the suspected line
}
if (contextstart <= 0) contextstart = 0;
const context = lines.slice(contextstart,contextend);
// Assemble the message
if (suspectedmissingsemicolon) {
const locincontext = semicolonmissingat - contextstart;
context[locincontext] += " ◀◀◀ MISSING SOMETHING?";
}
const message = [When compiling ${name}:
].concat(context);
if (triggeringindex >= 0) {
message.push(' '.repeat(triggering_index) + '▀'.repeat(triggering.length));
}
message.push(error);
let longest = 0;
for (const l of message) if (l.length > longest) longest = l.length;
message.unshift('='.repeat(longest+1));
return message.join('
');
}```