Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 82 additions & 16 deletions src/read.c
Original file line number Diff line number Diff line change
Expand Up @@ -2477,21 +2477,23 @@ static void RecreateStackNams(ReaderState * rs, Obj context)

/****************************************************************************
**
*F ReadEvalCommand() . . . . . . . . . . . . . . . . . . . read one command
**
** 'ReadEvalCommand' reads one command and interprets it immediately.
*F ReadEvalCommandInternal() . . . . . . read one command, evaluate or check
**
** It does not expect the first symbol of its input already read and won't
** read the first symbol of the next input.
** Shared implementation of 'ReadEvalCommand' and 'ReadCheckCommand'.
**
** If 'dualSemicolon' is a non-zero pointer, then the integer it points to
** will be set to 1 if the command was followed by a double semicolon, else
** it is set to 0. If 'dualSemicolon' is zero then it is ignored.
** If 'checkOnly' is set, the command is parsed but not executed: the
** interpreter is switched into ignoring mode for the duration, so no
** statement has any effect, and 'IntrEnd' is skipped. If 'syntaxErrors' is
** non-zero, it must be a plist; diagnostics are then appended to it as
** records instead of being printed (see 'ScannerState.errors').
*/
ExecStatus ReadEvalCommand(Obj context,
TypInputFile * input,
Obj * evalResult,
BOOL * dualSemicolon)
static ExecStatus ReadEvalCommandInternal(Obj context,
TypInputFile * input,
BOOL checkOnly,
Obj syntaxErrors,
Obj * evalResult,
BOOL * dualSemicolon,
BOOL * errorAtEOF)
{
volatile ExecStatus status;
volatile Obj tilde;
Expand All @@ -2507,6 +2509,7 @@ ExecStatus ReadEvalCommand(Obj context,

GAP_ASSERT(input);
rs->s.input = input;
rs->s.errors = syntaxErrors;

ClearError();

Expand All @@ -2516,6 +2519,8 @@ ExecStatus ReadEvalCommand(Obj context,
// if scanning the first symbol produced a syntax error, abort
if (rs->s.NrError) {
FlushRestOfInputLine(input);
if (errorAtEOF)
*errorAtEOF = rs->s.firstErrorAtEOF;
return STATUS_ERROR;
}

Expand All @@ -2539,8 +2544,9 @@ ExecStatus ReadEvalCommand(Obj context,
lockSP = RegionLockSP();
#endif

AssGVar(GVarName("READEVALCOMMAND_LINENUMBER"),
INTOBJ_INT(GetInputLineNumber(input)));
if (!checkOnly)
AssGVar(GVarName("READEVALCOMMAND_LINENUMBER"),
INTOBJ_INT(GetInputLineNumber(input)));

// remember the old execution state and start an execution environment
Bag oldLVars =
Expand All @@ -2554,6 +2560,12 @@ ExecStatus ReadEvalCommand(Obj context,
IntrBegin(&rs->intr);
rs->intr.gapnameid = GetInputFilenameID(input);

// in check mode, parse with the interpreter ignoring everything; every
// interpreter action keeps 'ignoring' balanced when it is already
// positive, so nothing is executed and nothing is left on the stack
if (checkOnly)
rs->intr.ignoring = 1;

switch (rs->s.Symbol) {
// read an expression or an assignment or a procedure call
case S_IDENT: ReadExpr(rs, S_SEMICOLON|S_EOF, 'x' ); break;
Expand Down Expand Up @@ -2588,8 +2600,13 @@ ExecStatus ReadEvalCommand(Obj context,
if (dualSemicolon)
*dualSemicolon = (rs->s.Symbol == S_DUALSEMICOLON);

// end the interpreter
status = IntrEnd(&rs->intr, rs->s.NrError > 0, evalResult);
// end the interpreter; in check mode 'IntrEnd' must be skipped (nothing
// was interpreted, so there is no result on the stack), the status is
// derived from the error count alone
if (checkOnly)
status = rs->s.NrError > 0 ? STATUS_ERROR : STATUS_END;
else
status = IntrEnd(&rs->intr, rs->s.NrError > 0, evalResult);

// restore the execution environment
SWITCH_TO_OLD_LVARS(oldLVars);
Expand All @@ -2611,10 +2628,59 @@ ExecStatus ReadEvalCommand(Obj context,

ClearError();

if (errorAtEOF)
*errorAtEOF = rs->s.firstErrorAtEOF;

// return whether a return-statement or a quit-statement were executed
return status;
}


/****************************************************************************
**
*F ReadEvalCommand() . . . . . . . . . . . . . . . . . . . read one command
**
** 'ReadEvalCommand' reads one command and interprets it immediately.
**
** It does not expect the first symbol of its input already read and won't
** read the first symbol of the next input.
**
** If 'dualSemicolon' is a non-zero pointer, then the integer it points to
** will be set to 1 if the command was followed by a double semicolon, else
** it is set to 0. If 'dualSemicolon' is zero then it is ignored.
*/
ExecStatus ReadEvalCommand(Obj context,
TypInputFile * input,
Obj * evalResult,
BOOL * dualSemicolon)
{
return ReadEvalCommandInternal(context, input, FALSE, 0, evalResult,
dualSemicolon, 0);
}


/****************************************************************************
**
*F ReadCheckCommand() . . . . . . . parse one command, but do not execute it
**
** 'ReadCheckCommand' parses one command from <input> without executing any
** of it and without printing anything. It returns 'STATUS_END' if a
** complete command was parsed, 'STATUS_EOF' if the input was exhausted
** before the first symbol of a command, and 'STATUS_ERROR' on a syntax
** error.
**
** Diagnostics are appended to <syntaxErrors> (a plist) if it is non-zero.
** On 'STATUS_ERROR', if <errorAtEOF> is non-zero, the value it points to is
** set to TRUE iff the first syntax error was caused by the input ending,
** i.e., the input is a truncated prefix of potentially valid input.
*/
ExecStatus
ReadCheckCommand(TypInputFile * input, Obj syntaxErrors, BOOL * errorAtEOF)
{
return ReadEvalCommandInternal(0, input, TRUE, syntaxErrors, 0, 0,
errorAtEOF);
}

/****************************************************************************
**
*F ReadEvalFile() . . . . . . . . . . . . . . . . . . . . . . . read a file
Expand Down
19 changes: 19 additions & 0 deletions src/read.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,25 @@ ExecStatus ReadEvalCommand(Obj context,
BOOL * dualSemicolon);


/****************************************************************************
**
*F ReadCheckCommand() . . . . . . . parse one command, but do not execute it
**
** 'ReadCheckCommand' parses one command from <input> without executing any
** of it and without printing anything. It returns 'STATUS_END' if a
** complete command was parsed, 'STATUS_EOF' if the input was exhausted
** before the first symbol of a command, and 'STATUS_ERROR' on a syntax
** error.
**
** Diagnostics are appended to <syntaxErrors> (a plist) if it is non-zero.
** On 'STATUS_ERROR', if <errorAtEOF> is non-zero, the value it points to is
** set to TRUE iff the first syntax error was caused by the input ending,
** i.e., the input is a truncated prefix of potentially valid input.
*/
ExecStatus
ReadCheckCommand(TypInputFile * input, Obj syntaxErrors, BOOL * errorAtEOF);


/****************************************************************************
**
*F ReadEvalFile() . . . . . . . . . . . . . . . . . . . . . . . read a file
Expand Down
71 changes: 67 additions & 4 deletions src/scanner.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@

#include "scanner.h"

#include "bool.h"
#include "error.h"
#include "gapstate.h"
#include "gaputils.h"
#include "io.h"
#include "lists.h"
#include "plist.h"
#include "precord.h"
#include "records.h"
#include "stringobj.h"
#include "sysstr.h"

Expand All @@ -30,6 +33,35 @@ static UInt NextSymbol(ScannerState * s);

#define GET_NEXT_CHAR() GetNextChar(s->input)

/****************************************************************************
**
*F NewSyntaxErrorRecord( <msg> ) . . . build a record describing a diagnostic
**
** Build a record describing a syntax error or warning, for collection into
** 'ScannerState.errors'. The positions are those the caret printer in
** 'SyntaxErrorOrWarning' uses.
*/
static Obj NewSyntaxErrorRecord(ScannerState * s,
const Char * msg,
UInt error,
Int tokenoffset)
{
Int pos = (tokenoffset == 0) ? GetInputLinePosition(s->input)
: s->SymbolStartPos[tokenoffset - 1];

Obj record = NEW_PREC(6);
AssPRec(record, RNamName("message"), MakeImmString(msg));
AssPRec(record, RNamName("isError"), error ? True : False);
AssPRec(record, RNamName("line"),
INTOBJ_INT(s->SymbolStartLine[tokenoffset]));
AssPRec(record, RNamName("pos"), INTOBJ_INT(s->SymbolStartPos[tokenoffset]));
AssPRec(record, RNamName("endLine"),
INTOBJ_INT(GetInputLineNumber(s->input)));
AssPRec(record, RNamName("endPos"), INTOBJ_INT(pos));
return record;
}


/****************************************************************************
**
*F SyntaxErrorOrWarning( <msg> ) . . . . . . raise a syntax error or warning
Expand All @@ -43,8 +75,22 @@ static void SyntaxErrorOrWarning(ScannerState * s,
Int tokenoffset)
{
GAP_ASSERT(tokenoffset >= 0 && tokenoffset <= 2);

// classify the first error: is the input merely truncated, i.e., could
// appending more text still produce valid input?
if (error && s->NrError == 0)
s->firstErrorAtEOF = (s->Symbol == S_EOF) || s->pendingEOFError;
s->pendingEOFError = FALSE;

// if diagnostics are being collected, record instead of printing; honour
// the same one-message-per-line gate as the printing branch below
if (s->errors) {
if (s->input->lastErrorLine != s->input->number)
PushPlist(s->errors,
NewSyntaxErrorRecord(s, msg, error, tokenoffset));
}
// do not print a message if we found one already on the current line
if (s->input->lastErrorLine != s->input->number) {
else if (s->input->lastErrorLine != s->input->number) {

// open error output
TypOutputFile output = { 0 };
Expand Down Expand Up @@ -475,10 +521,13 @@ static UInt GetNumber(ScannerState * s, Int readDecimalPoint, Char c)
seenADigit = TRUE;
c = GET_NEXT_CHAR();
}
if (!seenADigit)
if (!seenADigit) {
if (c == '\377')
s->pendingEOFError = TRUE;
SyntaxError(s,
"Badly formed number: need a digit before or after the "
"decimal point");
}
if (c == '\\')
SyntaxError(s, "Badly formed number");

Expand All @@ -495,9 +544,12 @@ static UInt GetNumber(ScannerState * s, Int readDecimalPoint, Char c)

// Here we are into the unsigned exponent of a number in scientific
// notation, so we just read digits
if (!IsDigit(c))
if (!IsDigit(c)) {
if (c == '\377')
s->pendingEOFError = TRUE;
SyntaxError(s, "Badly formed number: need at least one digit in "
"the exponent");
}
while (IsDigit(c)) {
i = AddCharToValue(s, i, c);
c = GET_NEXT_CHAR();
Expand Down Expand Up @@ -561,8 +613,11 @@ static inline Char GetOctalDigits(ScannerState * s, Char c)
Char result;
result = 8 * (c - '0');
c = GET_NEXT_CHAR();
if ( c < '0' || c > '7' )
if ( c < '0' || c > '7' ) {
if (c == '\377')
s->pendingEOFError = TRUE;
SyntaxError(s, "Expecting octal digit");
}
result = result + (c - '0');

return result;
Expand All @@ -578,6 +633,8 @@ static inline Char CharHexDigit(ScannerState * s)
{
Char c = GET_NEXT_CHAR();
if (!isxdigit((unsigned int)c)) {
if (c == '\377')
s->pendingEOFError = TRUE;
SyntaxError(s, "Expecting hexadecimal digit");
}
if (c >= 'a') {
Expand Down Expand Up @@ -622,6 +679,8 @@ static Char GetEscapedChar(ScannerState * s)
} else if (c >= '0' && c <= '7') {
result += GetOctalDigits(s, c);
} else {
if (c == '\377')
s->pendingEOFError = TRUE;
SyntaxError(s, "Expecting hexadecimal escape, or two more octal digits");
}
} else if ( c >= '1' && c <= '7' ) {
Expand Down Expand Up @@ -683,6 +742,7 @@ static Char GetStr(ScannerState * s, Char c)

if (c == '\377') {
FlushRestOfInputLine(s->input);
s->pendingEOFError = TRUE;
SyntaxError(s, "String must end with \" before end of file");
}

Expand Down Expand Up @@ -759,6 +819,7 @@ static Char GetTripStr(ScannerState * s, Char c)

if (c == '\377') {
FlushRestOfInputLine(s->input);
s->pendingEOFError = TRUE;
SyntaxError(s, "String must end with \"\"\" before end of file");
}

Expand Down Expand Up @@ -835,6 +896,8 @@ static void GetChar(ScannerState * s)
if ( c == '\'' ) {
c = GET_NEXT_CHAR();
} else {
if (c == '\377')
s->pendingEOFError = TRUE;
SyntaxError(s, "Missing single quote in character constant");
}
}
Expand Down
14 changes: 14 additions & 0 deletions src/scanner.h
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,20 @@ typedef struct {
// error occurred.
UInt NrError;

// If non-zero, this is a plist: syntax errors and warnings are appended
// to it as records instead of being printed to ERROR_OUTPUT.
Obj errors;

// One-shot flag set by token getters immediately before raising a
// SyntaxError caused by hitting end of input inside a token (e.g. an
// unterminated string). Consumed and reset by 'SyntaxErrorOrWarning'.
BOOL pendingEOFError;

// Set when the first syntax error is raised: TRUE iff that error was
// caused by end of input, i.e., the input is a truncated prefix of
// potentially valid input. Never reset afterwards.
BOOL firstErrorAtEOF;

} ScannerState;


Expand Down
Loading
Loading