Skip to content
This repository was archived by the owner on Aug 31, 2021. It is now read-only.

Commit 105fa1c

Browse files
authored
Merge pull request #4518 from peter-b/lc-compile-error-context
lc-compile: Print contextual information for each warning or error
2 parents e5c2649 + 5148f95 commit 105fa1c

File tree

6 files changed

+251
-30
lines changed

6 files changed

+251
-30
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# LiveCode Builder Tools
2+
## lc-compile
3+
### Messages
4+
5+
* Errors, warnings and informational messages now display the affected
6+
line of code and visually indicate the position where the problem
7+
was found.

tests/_compilertestrunner.livecodescript

Lines changed: 52 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ private command runCompilerTest pInfo, pScriptFile, pTest
233233
-- Process the assertions and make sure each one matches up
234234
local tTestOutput
235235
repeat for each line tAssertion in tTestInfo["assertions"]
236-
if doesCompilerOutputSatisfyAssertion(tCompilerOutput, tCompilerExitStatus, tAssertion, tTestInfo["positions"]) then
236+
if doesCompilerOutputSatisfyAssertion(tCompilerOutput, tCompilerExitStatus, tAssertion, tTestInfo) then
237237
put "ok - " after tTestOutput
238238
else
239239
put "not ok - " after tTestOutput
@@ -330,7 +330,7 @@ private function listCompilerTestsInFile pFilename
330330
return tTestNames
331331
end listCompilerTestsInFile
332332

333-
private function doesCompilerOutputSatisfyAssertion pCompilerOutput, pCompilerExitCode, pAssertion, pPositions
333+
private function doesCompilerOutputSatisfyAssertion pCompilerOutput, pCompilerExitCode, pAssertion, pTestInfo
334334
if item 1 of pAssertion is "success" then
335335
return pCompilerExitCode is 0
336336
end if
@@ -347,34 +347,63 @@ private function doesCompilerOutputSatisfyAssertion pCompilerOutput, pCompilerEx
347347
end if
348348

349349
set the itemDelimiter to ":"
350+
351+
local tState
352+
put "search" into tState
350353
repeat for each line tOutputLine in pCompilerOutput
351354
local tFile, tLine, tColumn, tType, tMessage
355+
local tSourceLine, tMarker
356+
357+
if tState is "search" then
358+
-- The format of each output line is:
359+
-- <file>:<line>:<col>: (error|warning): <msg>
360+
put item 1 of tOutputLine into tFile
361+
put item 2 of tOutputLine into tLine
362+
put item 3 of tOutputLine into tColumn
363+
put word 1 of item 4 of tOutputLine into tType
364+
put item 5 to -1 of tOutputLine into tMessage
365+
366+
-- If the assertion type doesn't match, continue
367+
if tAssertionType is not tType then
368+
next repeat
369+
end if
352370

353-
-- The format of each output line is:
354-
-- <file>:<line>:<col>: (error|warning): <msg>
355-
put item 1 of tOutputLine into tFile
356-
put item 2 of tOutputLine into tLine
357-
put item 3 of tOutputLine into tColumn
358-
put word 1 of item 4 of tOutputLine into tType
359-
put item 5 to -1 of tOutputLine into tMessage
371+
-- If the assertion message is not within the message, continue
372+
if tAssertionPartialMsg is not in tMessage then
373+
next repeat
374+
end if
360375

361-
-- If the assertion type doesn't match, continue
362-
if tAssertionType is not tType then
363-
next repeat
364-
end if
376+
-- If the position does not match, continue
377+
if pTestInfo["positions"][tAssertionPos] is not tLine then
378+
next repeat
379+
end if
365380

366-
-- If the assertion message is not within the message, continue
367-
if tAssertionPartialMsg is not in tMessage then
368-
next repeat
369-
end if
381+
-- If we get here, we have a match so are successful!
382+
put "source line" into tState
370383

371-
-- If the position does not match, continue
372-
if pPositions[tAssertionPos] is not tLine then
373-
next repeat
374-
end if
384+
else if tState is "source line" then
385+
-- The format of the source line output is a space followed
386+
-- by the corresponding line of the input source file.
387+
388+
put " " & line tLine of pTestInfo["code"] into tSourceLine
389+
390+
if tOutputLine is tSourceLine then
391+
put "marker" into tState
392+
end if
393+
394+
else if tState is "marker" then
395+
-- The marker should point at the correct column within the
396+
-- input source line
397+
398+
-- TODO the marker position should come from the position
399+
-- indicated in the code block, not from the position
400+
-- reported by the compiler.
401+
put format(merge("%[[tColumn]]s^"),"") into tMarker
375402

376-
-- If we get here, we have a match so are successful!
377-
return true
403+
if tOutputLine is tMarker then
404+
return true
405+
end if
406+
end if
378407
end repeat
379408

380409
-- We failed to find a suitable output line, so failure

toolchain/lc-compile/src/position.c

Lines changed: 152 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,15 @@ void GetRowOfPosition(long p_position, long *r_row)
7777
*r_row = ((p_position / COLUMNS_PER_ROW) % ROWS_PER_FILE) + 1;
7878
}
7979

80+
void GetRowTextOfPosition(long p_position, const char **r_text)
81+
{
82+
FileRef t_file = NULL;
83+
long t_row = 0;
84+
GetFileOfPosition(p_position, &t_file);
85+
GetRowOfPosition(p_position, &t_row);
86+
*r_text = GetFileLineText(t_file, t_row);
87+
}
88+
8089
void GetFileOfPosition(long p_position, FileRef *r_file)
8190
{
8291
long t_index;
@@ -226,8 +235,12 @@ struct File
226235
FileRef next;
227236
char *path;
228237
char *name;
229-
FILE *stream;
230238
unsigned int index;
239+
240+
int line_text_initialized;
241+
char *line_text_buffer;
242+
const char **line_text;
243+
int line_count;
231244
};
232245

233246
static FileRef s_files;
@@ -291,7 +304,9 @@ void AddFile(const char *p_filename)
291304
Fatal_OutOfMemory();
292305

293306
t_new_file -> index = s_next_file_index++;
294-
307+
308+
t_new_file->line_text_initialized = 0;
309+
295310
for(t_last_file_ptr = &s_files; *t_last_file_ptr != NULL; t_last_file_ptr = &((*t_last_file_ptr) -> next))
296311
;
297312

@@ -337,6 +352,141 @@ void GetFileName(FileRef p_file, const char **r_name)
337352
*r_name = p_file -> name;
338353
}
339354

355+
static int __FindNextSeparator(const char *p_buffer, int p_length,
356+
int p_start, int *r_sep)
357+
{
358+
for (*r_sep = p_start; *r_sep < p_length; ++*r_sep)
359+
{
360+
if (p_buffer[*r_sep] == '\n')
361+
return 1;
362+
363+
if (p_buffer[*r_sep] == '\r')
364+
{
365+
if (p_buffer[1+ *r_sep] == '\n')
366+
return 2;
367+
else
368+
return 1;
369+
}
370+
}
371+
return 0;
372+
}
373+
374+
/* Scan a file and build an array of line texts. This is expensive,
375+
* so it's performed lazily and _only_ if it's needed for printing
376+
* warning or error messages.
377+
*
378+
* As per SEPARATOR.t, \r, \n, and \r\n are all considered to be line
379+
* breaks.
380+
*/
381+
static void __InitializeFileLines(FileRef x_file)
382+
{
383+
FILE *t_stream = NULL;
384+
long t_file_length = 0;
385+
char *t_raw_text = NULL;
386+
const char **t_lines = NULL;
387+
int t_line_count = 0;
388+
int t_offset = 0;
389+
int t_last_offset = 0;
390+
int t_eol_length = 0;
391+
392+
if (0 != x_file->line_text_initialized)
393+
return;
394+
395+
x_file->line_text_initialized = 1;
396+
x_file->line_text_buffer = NULL;
397+
x_file->line_text = NULL;
398+
x_file->line_count = 0;
399+
400+
t_stream = fopen(x_file->path, "rb");
401+
402+
/* Allocate and fill a buffer with the entire file contents. */
403+
if (0 > fseek(t_stream, 0, SEEK_END))
404+
goto cleanup;
405+
t_file_length = ftell(t_stream);
406+
if (0 > t_file_length)
407+
goto cleanup;
408+
409+
if (0 > fseek(t_stream, 0, SEEK_SET))
410+
goto cleanup;
411+
412+
t_raw_text = malloc((t_file_length + 1) * sizeof(*t_raw_text));
413+
if (NULL == t_raw_text)
414+
Fatal_OutOfMemory();
415+
416+
if ((size_t) t_file_length != fread(t_raw_text, 1, t_file_length,
417+
t_stream))
418+
goto cleanup;
419+
t_raw_text[t_file_length] = 0; /* nul-terminate */
420+
421+
fclose(t_stream);
422+
423+
/* Scan the file contents twice: once to count the number of
424+
* lines, and once to fill a pointer array with offset
425+
* pointers. */
426+
t_line_count = 1;
427+
t_offset = 0;
428+
while (t_offset < t_file_length)
429+
{
430+
t_eol_length = __FindNextSeparator(t_raw_text, t_file_length,
431+
t_offset, &t_offset);
432+
++t_line_count;
433+
t_offset += t_eol_length;
434+
}
435+
436+
/* The lines array contains an extra, empty line at the end, which
437+
* is necessary because when lc-compile detects an error at the
438+
* end of the file (e.g. missing an "end module", it gives a
439+
* position *after* the last byte in the file. For consistency,
440+
* this "line" is actually a pointer to the additional nul byte
441+
* that was added to the end of the raw text buffer above. */
442+
t_lines = malloc((t_line_count + 1) * sizeof(*t_lines));
443+
if (NULL == t_lines)
444+
Fatal_OutOfMemory();
445+
446+
t_line_count = 0;
447+
t_offset = 0;
448+
t_last_offset = 0;
449+
while (t_offset < t_file_length)
450+
{
451+
t_eol_length = __FindNextSeparator(t_raw_text, t_file_length,
452+
t_offset, &t_offset);
453+
if (0 < t_eol_length)
454+
t_raw_text[t_offset] = 0; /* nul-terminate the line */
455+
t_offset += t_eol_length;
456+
457+
t_lines[t_line_count++] = (t_raw_text + t_last_offset);
458+
t_last_offset = t_offset;
459+
}
460+
/* Add the extra empty line */
461+
t_lines[t_line_count++] = (t_raw_text + t_file_length);
462+
463+
x_file->line_text_buffer = t_raw_text;
464+
t_raw_text = NULL;
465+
x_file->line_text = t_lines;
466+
t_lines = NULL;
467+
x_file->line_count = t_line_count;
468+
469+
Debug("Built message context data for %s (%i lines)",
470+
x_file->path, x_file->line_count);
471+
472+
cleanup:
473+
if (NULL != t_raw_text)
474+
free(t_raw_text);
475+
if (NULL != t_lines)
476+
free(t_lines);
477+
return;
478+
}
479+
480+
const char *GetFileLineText(FileRef p_file, long p_row)
481+
{
482+
__InitializeFileLines(p_file);
483+
if (p_file->line_text == NULL)
484+
return NULL;
485+
if (p_row > p_file->line_count)
486+
Fatal_InternalInconsistency("Examining line index beyond end of file");
487+
return p_file->line_text[p_row-1];
488+
}
489+
340490
void GetFileIndex(FileRef p_file, long *r_index)
341491
{
342492
*r_index = p_file -> index;

toolchain/lc-compile/src/position.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ void GetColumnOfPosition(PositionRef position, long *r_column);
4545
void GetRowOfPosition(PositionRef position, long *r_row);
4646
void GetFileOfPosition(PositionRef position, FileRef *r_file);
4747
void GetFilenameOfPosition(PositionRef position, const char **r_filename);
48+
void GetRowTextOfPosition(PositionRef position, const char **r_text);
4849

4950
void GetCurrentPosition(PositionRef *r_result);
5051
void yyGetPos(PositionRef *r_result);
@@ -60,6 +61,7 @@ int MoveToNextFile(void);
6061
void GetFilePath(FileRef file, const char **r_path);
6162
void GetFileName(FileRef file, const char **r_name);
6263
void GetFileIndex(FileRef file, long *r_index);
64+
const char *GetFileLineText(FileRef file, long p_row);
6365
int GetFileWithIndex(long index, FileRef *r_file);
6466
int GetCurrentFile(FileRef *r_file);
6567

toolchain/lc-compile/src/report.c

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,23 @@ void Fatal_InternalInconsistency(const char *p_message)
6161

6262
////////////////////////////////////////////////////////////////////////////////
6363

64+
void
65+
Debug(const char *p_format, ...)
66+
{
67+
va_list t_args;
68+
69+
if (s_verbose_level < 1)
70+
return;
71+
72+
va_start(t_args, p_format);
73+
74+
fprintf(stderr, "debug: ");
75+
vfprintf(stderr, p_format, t_args);
76+
fprintf(stderr, "\n");
77+
78+
va_end(t_args);
79+
}
80+
6481
void
6582
Debug_Emit(const char *p_format, ...)
6683
{
@@ -152,10 +169,27 @@ static void _PrintPosition(long p_position)
152169
fprintf(stderr, "%s:%ld:%ld: ", t_path, t_row, t_column);
153170
}
154171

172+
/* Print the source code line that contains p_position, and another
173+
* line below it with a caret pointing "up" to the exact character
174+
* that the position specifies. */
175+
static void _PrintContext(long p_position)
176+
{
177+
const char *t_text = NULL;
178+
long t_column;
179+
GetRowTextOfPosition(p_position, &t_text);
180+
GetColumnOfPosition(p_position, &t_column);
181+
if (NULL != t_text)
182+
{
183+
fprintf(stderr, " %s\n", t_text);
184+
fprintf(stderr, " %*c\n", (int)t_column, '^');
185+
}
186+
}
187+
155188
static void _Error(long p_position, const char *p_message)
156189
{
157190
_PrintPosition(p_position);
158191
fprintf(stderr, "error: %s\n", p_message);
192+
_PrintContext(p_position);
159193
s_error_count += 1;
160194
}
161195

@@ -172,6 +206,7 @@ static void _Warning(long p_position, const char *p_message)
172206
{
173207
_PrintPosition(p_position);
174208
fprintf(stderr, "warning: %s\n", p_message);
209+
_PrintContext(p_position);
175210
}
176211
}
177212

@@ -184,13 +219,12 @@ static void _ErrorS(long p_position, const char *p_message, const char *p_string
184219
fprintf(stderr, "error: ");
185220
fprintf(stderr, p_message, p_string);
186221
fprintf(stderr, "\n");
222+
_PrintContext(p_position);
187223
s_error_count += 1;
188224
}
189225

190226
static void _WarningS(long p_position, const char *p_message, const char *p_string)
191227
{
192-
long t_row, t_column;
193-
194228
if (IsDependencyCompile())
195229
return;
196230

@@ -200,12 +234,11 @@ static void _WarningS(long p_position, const char *p_message, const char *p_stri
200234
}
201235
else
202236
{
203-
GetColumnOfPosition(p_position, &t_column);
204-
GetRowOfPosition(p_position, &t_row);
205237
_PrintPosition(p_position);
206238
fprintf(stderr, "warning: ");
207239
fprintf(stderr, p_message, p_string);
208240
fprintf(stderr, "\n");
241+
_PrintContext(p_position);
209242
}
210243
}
211244

0 commit comments

Comments
 (0)