Skip to content

Commit aff422a

Browse files
committed
fix error tracking issues with user-defined functions and nested calls
1 parent 15645a0 commit aff422a

34 files changed

+723
-255
lines changed

EidosScribe/EidosConsoleWindowController.mm

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ - (void)validateSymbolTableAndFunctionMap
252252
- (NSString *)_executeScriptString:(NSString *)scriptString tokenString:(NSString **)tokenString parseString:(NSString **)parseString executionString:(NSString **)executionString errorString:(NSString **)errorString withOptionalSemicolon:(BOOL)semicolonOptional
253253
{
254254
std::string script_string([scriptString UTF8String]);
255-
EidosScript script(script_string, -1); // the position arguments are for debug points in QtSLiM, which are not supported here, so we can just pass -1
255+
EidosScript script(script_string);
256256
std::string output;
257257

258258
// Unfortunately, running readFromPopulationFile() is too much of a shock for SLiMgui. It invalidates variables that are being displayed in
@@ -531,12 +531,14 @@ - (void)executeScriptString:(NSString *)scriptString withOptionalSemicolon:(BOOL
531531
[outputTextView appendSpacer];
532532
}
533533

534-
if (errorString && !gEidosErrorContext.executingRuntimeScript &&
534+
// If we have an error, it is in the user script, and it has a valid position, then we can try to
535+
// highlight it in the input. Note that gEidosErrorContext.currentScript is nullptr in the console.
536+
if (errorString &&
537+
(!gEidosErrorContext.currentScript || (gEidosErrorContext.currentScript->UserScriptUTF16Offset() == 0)) &&
535538
(gEidosErrorContext.errorPosition.characterStartOfErrorUTF16 >= 0) &&
536539
(gEidosErrorContext.errorPosition.characterEndOfErrorUTF16 >= gEidosErrorContext.errorPosition.characterStartOfErrorUTF16) &&
537540
(scriptRange.location != NSNotFound))
538541
{
539-
// An error occurred, so let's try to highlight it in the input
540542
int errorTokenStart = gEidosErrorContext.errorPosition.characterStartOfErrorUTF16 + (int)scriptRange.location;
541543
int errorTokenEnd = gEidosErrorContext.errorPosition.characterEndOfErrorUTF16 + (int)scriptRange.location;
542544

@@ -592,8 +594,8 @@ - (BOOL)validateMenuItem:(NSMenuItem *)menuItem
592594
- (BOOL)checkScriptSuppressSuccessResponse:(BOOL)suppressSuccessResponse
593595
{
594596
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
595-
NSString *currentScriptString = [scriptTextView string];
596-
const char *cstr = [currentScriptString UTF8String];
597+
NSString *scriptString = [scriptTextView string];
598+
const char *cstr = [scriptString UTF8String];
597599
NSString *errorDiagnostic = nil;
598600

599601
if (!cstr)
@@ -602,7 +604,7 @@ - (BOOL)checkScriptSuppressSuccessResponse:(BOOL)suppressSuccessResponse
602604
}
603605
else
604606
{
605-
EidosScript script(cstr, -1);
607+
EidosScript script(cstr);
606608

607609
try {
608610
script.Tokenize();
@@ -685,9 +687,9 @@ - (IBAction)prettyprintScript:(id)sender
685687
if ([self checkScriptSuppressSuccessResponse:YES])
686688
{
687689
// We know the script is syntactically correct, so we can tokenize and parse it without worries
688-
NSString *currentScriptString = [scriptTextView string];
689-
const char *cstr = [currentScriptString UTF8String];
690-
EidosScript script(cstr, -1);
690+
NSString *scriptString = [scriptTextView string];
691+
const char *cstr = [scriptString UTF8String];
692+
EidosScript script(cstr);
691693

692694
script.Tokenize(false, true); // get whitespace and comment tokens
693695

EidosScribe/EidosTextView.mm

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ - (void)insertText:(id)insertString replacementRange:(NSRange)replacementRange
154154
// because of strings, which are a pain in the butt. To simplify that issue, we tokenize and search
155155
// in the token stream parallel to searching in the text.
156156
std::string script_string([scriptString UTF8String]);
157-
EidosScript script(script_string, -1);
157+
EidosScript script(script_string);
158158

159159
// Tokenize
160160
script.Tokenize(true, true); // make bad tokens as needed, keep nonsignificant tokens
@@ -766,9 +766,9 @@ - (BOOL)validateMenuItem:(NSMenuItem *)menuItem
766766

767767
- (void)selectErrorRange
768768
{
769-
// If there is error-tracking information set, and the error is not attributed to a runtime script
770-
// such as a lambda or a callback, then we can highlight the error range
771-
if (!gEidosErrorContext.executingRuntimeScript &&
769+
// If there is error-tracking information set, and the error is attributed to the user script,
770+
// then we can highlight the error range
771+
if ((!gEidosErrorContext.currentScript || (gEidosErrorContext.currentScript->UserScriptUTF16Offset() == 0)) &&
772772
(gEidosErrorContext.errorPosition.characterStartOfErrorUTF16 >= 0) &&
773773
(gEidosErrorContext.errorPosition.characterEndOfErrorUTF16 >= gEidosErrorContext.errorPosition.characterStartOfErrorUTF16))
774774
{
@@ -783,7 +783,7 @@ - (void)selectErrorRange
783783

784784
// In any case, since we are the ultimate consumer of the error information, we should clear out
785785
// the error state to avoid misattribution of future errors
786-
gEidosErrorContext = EidosErrorContext{{-1, -1, -1, -1}, nullptr, false};
786+
ClearErrorContext();
787787
}
788788

789789
- (void)setSelectedRanges:(NSArray *)ranges affinity:(NSSelectionAffinity)affinity stillSelecting:(BOOL)stillSelectingFlag
@@ -957,7 +957,7 @@ - (NSRange)selectionRangeForProposedRange:(NSRange)proposedCharRange granularity
957957
// because of strings, which are a pain in the butt. To simplify that issue, we tokenize and search
958958
// in the token stream parallel to searching in the text.
959959
std::string script_string([scriptString UTF8String]);
960-
EidosScript script(script_string, -1);
960+
EidosScript script(script_string);
961961

962962
// Tokenize
963963
script.Tokenize(true, true); // make bad tokens as needed, keep nonsignificant tokens
@@ -1091,7 +1091,7 @@ - (void)syntaxColorForEidos
10911091
// Construct a Script object from the current script string
10921092
NSString *scriptString = [self string];
10931093
std::string script_string([scriptString UTF8String]);
1094-
EidosScript script(script_string, -1);
1094+
EidosScript script(script_string);
10951095

10961096
// Tokenize
10971097
script.Tokenize(true, true); // make bad tokens as needed, keep nonsignificant tokens
@@ -1459,7 +1459,7 @@ - (EidosFunctionMap *)functionMapForScriptString:(NSString *)scriptString includ
14591459
// This returns a function map (owned by the caller) that reflects the best guess we can make, incorporating
14601460
// any functions known to our delegate, as well as all functions we can scrape from the script string.
14611461
std::string script_string([scriptString UTF8String]);
1462-
EidosScript script(script_string, -1);
1462+
EidosScript script(script_string);
14631463

14641464
// Tokenize
14651465
script.Tokenize(true, false); // make bad tokens as needed, don't keep nonsignificant tokens
@@ -1516,7 +1516,7 @@ - (NSAttributedString *)attributedSignatureForScriptString:(NSString *)scriptStr
15161516
if ([scriptString length])
15171517
{
15181518
std::string script_string([scriptString UTF8String]);
1519-
EidosScript script(script_string, -1);
1519+
EidosScript script(script_string);
15201520

15211521
// Tokenize
15221522
script.Tokenize(true, false); // make bad tokens as needed, don't keep nonsignificant tokens
@@ -2318,7 +2318,7 @@ - (void)_completionHandlerWithRangeForCompletion:(NSRange *)baseRange completion
23182318
delete definitive_function_map;
23192319

23202320
// Next, add type table entries based on parsing and analysis of the user's code
2321-
EidosScript script(script_string, -1);
2321+
EidosScript script(script_string);
23222322

23232323
#if EIDOS_DEBUG_COMPLETION
23242324
std::cout << "Eidos script:\n" << script_string << std::endl << std::endl;
@@ -2343,7 +2343,7 @@ - (void)_completionHandlerWithRangeForCompletion:(NSRange *)baseRange completion
23432343
#endif
23442344

23452345
// Tokenize; we can't use the tokenization done above, as we want whitespace tokens here...
2346-
EidosScript script(script_string, -1);
2346+
EidosScript script(script_string);
23472347
script.Tokenize(true, true); // make bad tokens as needed, keep nonsignificant tokens
23482348

23492349
#if EIDOS_DEBUG_COMPLETION

QtSLiM/QtSLiMConsoleTextEdit.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -210,11 +210,12 @@ void QtSLiMConsoleTextEdit::appendExecution(QString result, QString errorString,
210210
textCursor().setBlockFormat(marginBlockFormat);
211211
insertPlainText(errorString);
212212

213-
if (!gEidosErrorContext.executingRuntimeScript &&
214-
(gEidosErrorContext.errorPosition.characterStartOfErrorUTF16 >= 0) &&
215-
(gEidosErrorContext.errorPosition.characterEndOfErrorUTF16 >= gEidosErrorContext.errorPosition.characterStartOfErrorUTF16))
213+
// If we have an error, it is in the user script, and it has a valid position,
214+
// then we can try to highlight it in the input
215+
if ((!gEidosErrorContext.currentScript || (gEidosErrorContext.currentScript->UserScriptUTF16Offset() == 0)) &&
216+
(gEidosErrorContext.errorPosition.characterStartOfErrorUTF16 >= 0) &&
217+
(gEidosErrorContext.errorPosition.characterEndOfErrorUTF16 >= gEidosErrorContext.errorPosition.characterStartOfErrorUTF16))
216218
{
217-
// An error occurred, so let's try to highlight it in the input
218219
int promptEnd = lastPromptCursor.position();
219220
int errorTokenStart = gEidosErrorContext.errorPosition.characterStartOfErrorUTF16 + promptEnd;
220221
int errorTokenEnd = gEidosErrorContext.errorPosition.characterEndOfErrorUTF16 + promptEnd;

QtSLiM/QtSLiMEidosConsole.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ QString QtSLiMEidosConsole::_executeScriptString(QString scriptString, QString *
282282
scriptString.replace(QChar::LineSeparator, "\n");
283283

284284
std::string script_string(scriptString.toStdString());
285-
EidosScript script(script_string, -1);
285+
EidosScript script(script_string);
286286
std::string output;
287287

288288
// Unfortunately, running readFromPopulationFile() is too much of a shock for SLiMgui. It invalidates variables that are being displayed in

QtSLiM/QtSLiMEidosPrettyprinter.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -759,7 +759,7 @@ bool Eidos_reformatTokensFromScript(const std::vector<EidosToken> &tokens, Eidos
759759
// We're done reformatting; now we need to generate a new script and a new token stream, and fix the indentation of our
760760
// reformatted string by calling Eidos_prettyprintTokensFromScript(); this avoids duplicating a bunch of logic
761761
try {
762-
EidosScript indentScript(pretty, -1);
762+
EidosScript indentScript(pretty);
763763

764764
indentScript.Tokenize(false, true); // get whitespace and comment tokens
765765

QtSLiM/QtSLiMScriptTextEdit.cpp

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -221,10 +221,14 @@ void QtSLiMTextEdit::highlightError(int startPosition, int endPosition)
221221

222222
void QtSLiMTextEdit::selectErrorRange(EidosErrorContext &errorContext)
223223
{
224-
// If there is error-tracking information set, and the error is not attributed to a runtime script
225-
// such as a lambda or a callback, then we can highlight the error range
226-
if (!errorContext.executingRuntimeScript && (errorContext.errorPosition.characterStartOfErrorUTF16 >= 0) && (errorContext.errorPosition.characterEndOfErrorUTF16 >= errorContext.errorPosition.characterStartOfErrorUTF16))
227-
highlightError(errorContext.errorPosition.characterStartOfErrorUTF16, errorContext.errorPosition.characterEndOfErrorUTF16 + 1);
224+
// If there is error-tracking information set, and the error is attributed to the user script,
225+
// then we can highlight the error range
226+
if ((!gEidosErrorContext.currentScript || (gEidosErrorContext.currentScript->UserScriptUTF16Offset() == 0)) &&
227+
(errorContext.errorPosition.characterStartOfErrorUTF16 >= 0) &&
228+
(errorContext.errorPosition.characterEndOfErrorUTF16 >= errorContext.errorPosition.characterStartOfErrorUTF16))
229+
{
230+
highlightError(errorContext.errorPosition.characterStartOfErrorUTF16, errorContext.errorPosition.characterEndOfErrorUTF16 + 1);
231+
}
228232
}
229233

230234
QPalette QtSLiMTextEdit::qtslimStandardPalette(void)
@@ -288,8 +292,7 @@ bool QtSLiMTextEdit::checkScriptSuppressSuccessResponse(bool suppressSuccessResp
288292
{
289293
// Note this does *not* check out scriptString, which represents the state of the script when the Community object was created
290294
// Instead, it checks the current script in the script TextView – which is not used for anything until the recycle button is clicked.
291-
QString currentScriptString = toPlainText();
292-
QByteArray utf8bytes = currentScriptString.toUtf8();
295+
QByteArray utf8bytes = toPlainText().toUtf8();
293296
const char *cstr = utf8bytes.constData();
294297
std::string errorDiagnostic;
295298

@@ -301,7 +304,7 @@ bool QtSLiMTextEdit::checkScriptSuppressSuccessResponse(bool suppressSuccessResp
301304
{
302305
if (scriptType == EidosScriptType)
303306
{
304-
EidosScript script(cstr, -1);
307+
EidosScript script(cstr);
305308

306309
try {
307310
script.Tokenize();
@@ -342,7 +345,7 @@ bool QtSLiMTextEdit::checkScriptSuppressSuccessResponse(bool suppressSuccessResp
342345

343346
EidosErrorContext errorContext = gEidosErrorContext;
344347

345-
gEidosErrorContext = EidosErrorContext{{-1, -1, -1, -1}, nullptr, false};
348+
ClearErrorContext();
346349

347350
selectErrorRange(errorContext);
348351

@@ -406,10 +409,9 @@ void QtSLiMTextEdit::_prettyprint_reformat(bool p_reformat)
406409
if (checkScriptSuppressSuccessResponse(true))
407410
{
408411
// We know the script is syntactically correct, so we can tokenize and parse it without worries
409-
QString currentScriptString = toPlainText();
410-
QByteArray utf8bytes = currentScriptString.toUtf8();
412+
QByteArray utf8bytes = toPlainText().toUtf8();
411413
const char *cstr = utf8bytes.constData();
412-
EidosScript script(cstr, -1);
414+
EidosScript script(cstr);
413415

414416
script.Tokenize(false, true); // get whitespace and comment tokens
415417

@@ -736,7 +738,7 @@ EidosFunctionMap *QtSLiMTextEdit::functionMapForScriptString(QString scriptStrin
736738
// This returns a function map (owned by the caller) that reflects the best guess we can make, incorporating
737739
// any functions known to our delegate, as well as all functions we can scrape from the script string.
738740
std::string script_string = scriptString.toStdString();
739-
EidosScript script(script_string, -1);
741+
EidosScript script(script_string);
740742

741743
// Tokenize
742744
script.Tokenize(true, false); // make bad tokens as needed, don't keep nonsignificant tokens
@@ -825,7 +827,7 @@ EidosCallSignature_CSP QtSLiMTextEdit::signatureForScriptSelection(QString &call
825827
if (scriptString.length())
826828
{
827829
std::string script_string = scriptString.toStdString();
828-
EidosScript script(script_string, -1);
830+
EidosScript script(script_string);
829831

830832
// Tokenize
831833
script.Tokenize(true, false); // make bad tokens as needed, don't keep nonsignificant tokens
@@ -2323,7 +2325,7 @@ void QtSLiMTextEdit::_completionHandlerWithRangeForCompletion(NSRange *baseRange
23232325
delete definitive_function_map;
23242326

23252327
// Next, add type table entries based on parsing and analysis of the user's code
2326-
EidosScript script(script_string, -1);
2328+
EidosScript script(script_string);
23272329

23282330
script.Tokenize(true, false); // make bad tokens as needed, do not keep nonsignificant tokens
23292331
script.ParseInterpreterBlockToAST(true, true); // make bad nodes as needed (i.e. never raise, and produce a correct tree)
@@ -2334,7 +2336,7 @@ void QtSLiMTextEdit::_completionHandlerWithRangeForCompletion(NSRange *baseRange
23342336
}
23352337

23362338
// Tokenize; we can't use the tokenization done above, as we want whitespace tokens here...
2337-
EidosScript script(script_string, -1);
2339+
EidosScript script(script_string);
23382340
script.Tokenize(true, true); // make bad tokens as needed, keep nonsignificant tokens
23392341

23402342
const std::vector<EidosToken> &tokens = script.Tokens();

QtSLiM/QtSLiMSyntaxHighlighting.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ void QtSLiMScriptHighlighter::highlightBlock(__attribute__((__unused__)) const Q
200200
// set up a new cached script if we don't have one
201201
if (!script)
202202
{
203-
script = new EidosScript(document()->toPlainText().toUtf8().constData(), -1);
203+
script = new EidosScript(document()->toPlainText().toUtf8().constData());
204204

205205
script->Tokenize(true, true); // make bad tokens as needed, keep nonsignificant tokens
206206

QtSLiM/QtSLiMWindow.cpp

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1217,9 +1217,9 @@ bool QtSLiMWindow::isScriptModified(void)
12171217
if (scriptChangeObserved)
12181218
return true;
12191219

1220-
QString currentScript = ui->scriptTextEdit->toPlainText();
1220+
QString curScriptString = ui->scriptTextEdit->toPlainText();
12211221

1222-
if (lastSavedString != currentScript)
1222+
if (lastSavedString != curScriptString)
12231223
{
12241224
scriptChangeObserved = true; // sticky until saved
12251225
return true;
@@ -2155,7 +2155,7 @@ void QtSLiMWindow::checkForSimulationTermination(void)
21552155
// Get the error position and clear the global
21562156
EidosErrorContext errorContext = gEidosErrorContext;
21572157

2158-
gEidosErrorContext = EidosErrorContext{{-1, -1, -1, -1}, nullptr, false};
2158+
ClearErrorContext();
21592159

21602160
// Send the signal, which connects up to QtSLiMWindow::showTerminationMessage() through a Qt::QueuedConnection
21612161
emit terminationWithMessage(message, errorContext);
@@ -2275,7 +2275,7 @@ void QtSLiMWindow::startNewSimulationFromScript(void)
22752275
{
22762276
Species *species = (block->species_spec_ ? block->species_spec_ : (block->ticks_spec_ ? block->ticks_spec_ : nullptr));
22772277

2278-
if (species && (block->user_script_line_offset_ != -1) && block->root_node_ && block->root_node_->token_)
2278+
if (species && !block->script_ && block->root_node_ && block->root_node_->token_)
22792279
{
22802280
EidosToken *block_root_token = block->root_node_->token_;
22812281
int startPos = block_root_token->token_UTF16_start_;
@@ -2831,7 +2831,7 @@ void QtSLiMWindow::updateAfterTickFull(bool fullUpdate)
28312831
gEidosTermination.clear();
28322832
gEidosTermination.str("");
28332833
gEidosTermination << "ERROR (Eidos_FlushFiles): A compressed file buffer failed to write out to disk. Please check file paths, filesystem writeability and permissions, available disk space, and other possible causes of file I/O problems.\n";
2834-
gEidosErrorContext = EidosErrorContext{{-1, -1, -1, -1}, nullptr, false};
2834+
ClearErrorContext();
28352835
}
28362836
}
28372837

@@ -5441,8 +5441,8 @@ void QtSLiMWindow::debugOutputClicked(void)
54415441
void QtSLiMWindow::jumpToPopupButtonRunMenu(void)
54425442
{
54435443
QPlainTextEdit *scriptTE = ui->scriptTextEdit;
5444-
QString currentScriptString = scriptTE->toPlainText();
5445-
QByteArray utf8bytes = currentScriptString.toUtf8();
5444+
QString jumpScriptString = scriptTE->toPlainText();
5445+
QByteArray utf8bytes = jumpScriptString.toUtf8();
54465446
const char *cstr = utf8bytes.constData();
54475447
bool failedParse = true;
54485448

@@ -5664,7 +5664,7 @@ void QtSLiMWindow::jumpToPopupButtonRunMenu(void)
56645664
SLiMEidosBlock *new_script_block = new SLiMEidosBlock(script_block_node);
56655665
int32_t decl_start = new_script_block->root_node_->token_->token_UTF16_start_;
56665666
int32_t code_start = new_script_block->compound_statement_node_->token_->token_UTF16_start_;
5667-
QString decl = currentScriptString.mid(decl_start, code_start - decl_start);
5667+
QString decl = jumpScriptString.mid(decl_start, code_start - decl_start);
56685668

56695669
// Remove everything including and after the first newline
56705670
if (decl.indexOf(QChar::LineFeed) != -1)
@@ -5788,8 +5788,8 @@ void QtSLiMWindow::setScriptBlockLabelTextFromSelection(void)
57885788
// this does a subset of the parsing logic of QtSLiMWindow::jumpToPopupButtonRunMenu()
57895789
// it is used to get the label text for the script block label, to the right of the Jump button
57905790
QPlainTextEdit *scriptTE = ui->scriptTextEdit;
5791-
QString currentScriptString = scriptTE->toPlainText();
5792-
QByteArray utf8bytes = currentScriptString.toUtf8();
5791+
QString curScriptString = scriptTE->toPlainText();
5792+
QByteArray utf8bytes = curScriptString.toUtf8();
57935793
const char *cstr = utf8bytes.constData();
57945794

57955795
QTextCursor selection_cursor(scriptTE->textCursor());
@@ -5851,7 +5851,7 @@ void QtSLiMWindow::setScriptBlockLabelTextFromSelection(void)
58515851
if ((selStart >= decl_start) && (selStart <= code_end) && (selEnd <= code_end + 2)) // +2 allows a selection through the end brace and one more character (typically a newline)
58525852
{
58535853
int32_t code_start = new_script_block->compound_statement_node_->token_->token_UTF16_start_;
5854-
QString decl = currentScriptString.mid(decl_start, code_start - decl_start);
5854+
QString decl = curScriptString.mid(decl_start, code_start - decl_start);
58555855

58565856
// Remove everything including and after the first newline
58575857
if (decl.indexOf(QChar::LineFeed) != -1)

0 commit comments

Comments
 (0)