From 5841c2c17a0d349f5c14c14c9c6dc461778341e3 Mon Sep 17 00:00:00 2001 From: Sei Lisa Date: Sat, 25 May 2024 20:48:30 +0200 Subject: [PATCH] Introduce 'c' (constant) as symbol kind This is preparatory work for addressing issue #30. It also fixes a bug where --prettify made the constants writable. --- lslopt/lslparse.py | 41 ++++++++++++++++++++++++++++------------- run-tests.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 13 deletions(-) diff --git a/lslopt/lslparse.py b/lslopt/lslparse.py index 4b05b71..2bcec67 100644 --- a/lslopt/lslparse.py +++ b/lslopt/lslparse.py @@ -1009,7 +1009,7 @@ class parser(object): return nr(nt='FNCALL', t=sym['Type'], name=name, ch=args) sym = self.FindSymbolFull(val) - if sym is None or sym['Kind'] != 'v': + if sym is None or sym['Kind'] not in {'v', 'c'}: self.errorpos = savepos raise EParseUndefined(self) @@ -1052,9 +1052,8 @@ class parser(object): self.AddSymbol('f', 0, 'lazy_list_set', Loc=self.usedspots, Type='list', ParamTypes=params[0], ParamNames=params[1], Inline=False) - self.AddSymbol('v', paramscope, 'L', Type='list') - self.AddSymbol('v', paramscope, 'i', Type='integer') - self.AddSymbol('v', paramscope, 'v', Type='list') + for typ, name in zip(*params): + self.AddSymbol('v', paramscope, name, Type=typ) #self.PushScope() # no locals # Add body (apologies for the wall of text) @@ -1187,6 +1186,8 @@ list lazy_list_set(list L, integer i, list v) )]) if tok0 == '.': + if sym['Kind'] == 'c': + raise EParseSyntax(self) self.NextToken() self.expect('IDENT') self.ValidateField(typ, self.tok[1]) @@ -1196,6 +1197,8 @@ list lazy_list_set(list L, integer i, list v) typ = 'float' if tok0 in ('++', '--'): + if sym['Kind'] == 'c': + raise EParseSyntax(self) self.NextToken() if lvalue.t not in ('integer', 'float'): raise EParseTypeMismatch(self) @@ -1204,6 +1207,8 @@ list lazy_list_set(list L, integer i, list v) if AllowAssignment and (tok0 in self.assignment_toks or self.extendedassignment and tok0 in self.extassignment_toks): + if sym['Kind'] == 'c': + raise EParseSyntax(self) self.NextToken() expr = self.Parse_expression() rtyp = expr.t @@ -1765,6 +1770,8 @@ list lazy_list_set(list L, integer i, list v) self.NextToken() self.expect('IDENT') name = self.tok[1] + if name in self.symtab[0] and self.symtab[0][name]['Kind'] == 'c': + raise EParseSyntax(self) if name in self.symtab[self.scopeindex]: raise EParseAlreadyDefined(self) # shrinknames *needs* all labels renamed, so they are out of the way @@ -2258,6 +2265,8 @@ list lazy_list_set(list L, integer i, list v) self.NextToken() self.expect('IDENT') name = self.tok[1] + if name in self.symtab[0] and self.symtab[0][name]['Kind'] == 'c': + raise EParseSyntax(self) if name in self.symtab[self.scopeindex]: raise EParseAlreadyDefined(self) self.NextToken() @@ -2354,8 +2363,8 @@ list lazy_list_set(list L, integer i, list v) sym = self.FindSymbolPartial(tok[1]) # The parser accepts library function names here as valid variables # (it chokes at RAIL in Mono, and at runtime in LSO for some types) - if sym is None or sym['Kind'] != 'v' and (sym['Kind'] != 'f' - or 'ParamNames' in sym): # only UDFs have ParamNames + if sym is None or sym['Kind'] not in {'v', 'c'} and (sym['Kind'] != + 'f' or 'ParamNames' in sym): # only UDFs have ParamNames raise EParseUndefined(self) typ = sym['Type'] if ForbidList and lslcommon.LSO and typ == 'key': @@ -2429,6 +2438,10 @@ list lazy_list_set(list L, integer i, list v) self.expect('IDENT') name = self.tok[1] + if (name in self.symtab[0] + and self.symtab[0][name]['Kind'] == 'c' + ): + raise EParseSyntax(self) if name in self.symtab[self.scopeindex]: raise EParseAlreadyDefined(self) @@ -2928,14 +2941,16 @@ list lazy_list_set(list L, integer i, list v) # The first element (0) is the global scope. Each symbol table is a # dictionary of symbols, whose elements are in turn dictionaries of # attributes. Each has a 'Kind', which can be: - # 'v' for variable, 'f' for function, 'l' for label, 's' for state, - # or 'e' for event. Some have a 'Loc' indicating the location (index) - # of the definition in the tree root. - # Variables have 'Scope' and 'Type' (a string). + # 'v' for variable, 'c' for constant, 'f' for function, 'l' for label, + # 's' for state, or 'e' for event. Some have a 'Loc' indicating the + # location (index) of the definition in the tree's root. + # Variables and constants have 'Scope' and 'Type' (a string). # Global variables also have 'Loc'. # Variables that are parameters also have 'Param'. - # Functions have 'Type' (return type, a string) and 'ParamTypes' (a list of strings). - # User-defined functions also have 'Loc' and 'ParamNames' (a list of strings). + # Functions have 'Type' (return type, a string) and 'ParamTypes' (a + # list of strings). + # User-defined functions also have 'Loc' and 'ParamNames' (a list of + # strings). # Labels only have 'Scope'. # States only have 'Loc'. # Events have 'ParamTypes' and 'ParamNames', just like UDFs. @@ -2953,7 +2968,7 @@ list lazy_list_set(list L, integer i, list v) if self.prettify: # Add the constants as symbol table variables... for i in self.constants: - self.symtab[0][i] = {'Kind':'v', 'Scope':0, + self.symtab[0][i] = {'Kind':'c', 'Scope':0, 'Type':lslcommon.PythonType2LSL[type(self.constants[i])]} # ... and remove them as constants. self.constants = {} diff --git a/run-tests.py b/run-tests.py index 9fbbc2b..b680aea 100755 --- a/run-tests.py +++ b/run-tests.py @@ -418,6 +418,48 @@ class UnitTestRegression(UnitTestCase): self.assertRaises(lslparse.EParseTypeMismatch, parser.parse, 'default{timer(){if(1)return 1;}}') + # Constants with 'c' in symbol table (introduced to solve issue #30) + # Some errors are not the expected ones. Most (all?) should be syntax. + parser.parse('integer a=LOOP;default{timer(){llOwnerSay(NAK+EOF);}}') + parser.parse('integer a=LOOP;default{timer(){llOwnerSay(NAK+EOF);}}', + ('prettify',)) + parser.parse('default{timer(){LOOP;}}') + parser.parse('default{timer(){LOOP;}}', ('prettify',)) + self.assertRaises(lslparse.EParseSyntax, parser.parse, + 'default{touch(integer LOOP){}}', ('prettify',)) + self.assertRaises(lslparse.EParseSyntax, parser.parse, + 'default{timer(){ZERO_VECTOR.x;}}', ('prettify',)) + self.assertRaises(lslparse.EParseSyntax, parser.parse, + 'default{timer(){LOOP.x;}}', ('prettify',)) + self.assertRaises(lslparse.EParseSyntax, parser.parse, + 'default{timer(){LOOP=1;}}', ('prettify',)) + self.assertRaises(lslparse.EParseSyntax, parser.parse, + 'default{timer(){LOOP++;}}', ('prettify',)) + # should raise EParseSyntax instead + self.assertRaises(lslparse.EParseUndefined, parser.parse, + 'default{timer(){++LOOP;}}', ('prettify',)) + # should raise EParseSyntax instead + self.assertRaises(lslparse.EParseAlreadyDefined, parser.parse, + 'integer LOOP=0;', ('prettify',)) + # should raise EParseSyntax instead + self.assertRaises(lslparse.EParseAlreadyDefined, parser.parse, + 'integer LOOP(){}', ('prettify',)) + self.assertRaises(lslparse.EParseSyntax, parser.parse, + 'default{timer(){integer LOOP=1;}}', ('prettify',)) + self.assertRaises(lslparse.EParseSyntax, parser.parse, + 'default{timer(){integer LOOP;}}', ('prettify',)) + # should raise EParseSyntax instead + self.assertRaises(lslparse.EParseAlreadyDefined, parser.parse, + 'default{timer(){}}state LOOP{timer(){}}', ('prettify',)) + # should raise EParseSyntax instead + self.assertRaises(lslparse.EParseUndefined, parser.parse, + 'default{timer(){state LOOP;}}', ('prettify',)) + self.assertRaises(lslparse.EParseSyntax, parser.parse, + 'default{timer(){@LOOP;}}', ('prettify',)) + self.assertRaises(lslparse.EParseSyntax, parser.parse, + 'default{timer(){integer LOOP;}}', ('prettify',)) + + class UnitTestCoverage(UnitTestCase): def test_coverage_misc(self): """Miscellaneous tests that can't be computed or are too difficult