Fix EParseCantChangeState so that it is always properly reported.

Still somewhat messy, but still reported as soon as it can be detected.

If an ELSE token is detected at the top level, for example, the error position will be rewound to the state change and reported there.

This means that in this situation:

x()
{
    if (1)
    {
        state default;
        x(2);
    }
    else ;
}
default{timer(){}}

an error will be reported in x(2), because the ELSE hasn't been found at that point, therefore the state change statement isn't found to be at fault yet.

However, in this case:

x()
{
    if (1)
        state default;
    else
        x(2);
}
default{timer(){}}

the error WILL be reported at the state change statement.

This commit also changes the position where the exception is reported, to be at the STATE token. As an inconsequential side effect, EParseCantChangeState takes precedence over undefined identifiers, in case the state change is to an undefined state, but only in cases where it can be immediately detected.
This commit is contained in:
Sei Lisa 2017-11-02 13:31:06 +01:00
parent 097c054494
commit ef6ed30536

View file

@ -1622,6 +1622,11 @@ list lazy_list_set(list L, integer i, list v)
return jumpnode return jumpnode
if tok0 == 'STATE': if tok0 == 'STATE':
if self.localevents is None:
if AllowStSw is False:
raise EParseCantChangeState(self)
if AllowStSw is None:
self.SuspiciousStSw.append(self.errorpos)
self.NextToken() self.NextToken()
if self.tok[0] not in ('DEFAULT', 'IDENT'): if self.tok[0] not in ('DEFAULT', 'IDENT'):
raise EParseSyntax(self) raise EParseSyntax(self)
@ -1632,8 +1637,6 @@ list lazy_list_set(list L, integer i, list v)
self.NextToken() self.NextToken()
self.expect(';') self.expect(';')
self.NextToken() self.NextToken()
if self.localevents is None and not AllowStSw:
raise EParseCantChangeState(self)
return {'nt':'STSW', 't':None, 'name':name, 'scope':0} return {'nt':'STSW', 't':None, 'name':name, 'scope':0}
if tok0 == 'RETURN': if tok0 == 'RETURN':
@ -1666,20 +1669,21 @@ list lazy_list_set(list L, integer i, list v)
ret['ch'].append(self.Parse_expression()) ret['ch'].append(self.Parse_expression())
self.expect(')') self.expect(')')
self.NextToken() self.NextToken()
# INCOMPATIBILITY NOTE: This is more permissive than LSL. saveSuspiciousStSw = self.SuspiciousStSw
# In LSL, an if...then...else does NOT allow a state change self.SuspiciousStSw = []
# in either branch. Only an if...then without else does. ret['ch'].append(self.Parse_statement(ReturnType, AllowStSw = None, InsideLoop = InsideLoop))
# BUT since the decision to allow or not needs to be taken before
# the 'else' is found, we're not going to check the branch after
# parsing, only for the sake of reporting that error. The compiler
# will report it.
ret['ch'].append(self.Parse_statement(ReturnType, AllowStSw = True, InsideLoop = InsideLoop))
if self.tok[0] == 'ELSE': if self.tok[0] == 'ELSE':
if AllowStSw is False and self.SuspiciousStSw:
self.errorpos = self.SuspiciousStSw[0]
raise EParseCantChangeState(self)
LastIsReturn = 'LIR' in ret['ch'][1] LastIsReturn = 'LIR' in ret['ch'][1]
self.NextToken() self.NextToken()
ret['ch'].append(self.Parse_statement(ReturnType, AllowStSw = AllowStSw, InsideLoop = InsideLoop)) ret['ch'].append(self.Parse_statement(ReturnType, AllowStSw = AllowStSw, InsideLoop = InsideLoop))
if AllowStSw is None:
saveSuspiciousStSw += self.SuspiciousStSw
if LastIsReturn and 'LIR' in ret['ch'][2]: if LastIsReturn and 'LIR' in ret['ch'][2]:
ret['LIR'] = True ret['LIR'] = True
self.SuspiciousStSw = saveSuspiciousStSw
return ret return ret
if tok0 == 'WHILE': if tok0 == 'WHILE':
@ -2652,6 +2656,9 @@ list lazy_list_set(list L, integer i, list v)
# List of preprocessor #line directives. # List of preprocessor #line directives.
self.linedir = [] self.linedir = []
# List of positions with suspicious state change statements.
self.SuspiciousStSw = []
# This is a small hack to prevent circular definitions in globals when # This is a small hack to prevent circular definitions in globals when
# extended expressions are enabled. When false (default), forward # extended expressions are enabled. When false (default), forward
# globals are allowed; if true, only already seen globals are permitted. # globals are allowed; if true, only already seen globals are permitted.