diff --git a/lslopt/lslparse.py b/lslopt/lslparse.py index be3b384..87119f6 100644 --- a/lslopt/lslparse.py +++ b/lslopt/lslparse.py @@ -1,3 +1,4 @@ + from lslcommon import Key, Vector, Quaternion import lslfuncs import sys, re @@ -89,6 +90,11 @@ class EParseDeclarationScope(EParse): super(EParseDeclarationScope, self).__init__(parser, u"Declaration requires a new scope -- use { and }") +class EParseDuplicateLabel(EParse): + def __init__(self, parser): + super(EParseDuplicateLabel, self).__init__(parser, + u"Duplicate local label name. That won't allow the Mono script to be saved, and will not work as expected in LSO.") + class EInternal(Exception): """This exception is a construct to allow a different function to cause an immediate return of EOF from parser.GetToken(). Reused elsewhere for @@ -1150,6 +1156,10 @@ class parser(object): name = self.tok[1] if name in self.symtab[self.scopeindex]: raise EParseAlreadyDefined(self) + if not self.duplabels and name in self.locallabels: + raise EParseDuplicateLabel(self) + # All labels go to a common pool local to the current function. + self.locallabels.add(name) self.AddSymbol('l', self.scopeindex, name) self.NextToken() self.expect(';') @@ -1441,7 +1451,9 @@ class parser(object): raise EParseSyntax(self) self.expect(')') self.NextToken() + self.locallabels = set() body = self.Parse_code_block(None) + del self.locallabels ret.append({'nt':'FNDEF', 't':None, 'name':name, 'pscope':self.scopeindex, 'ptypes':params[0], 'pnames':params[1], 'ch':[body]}) @@ -1519,7 +1531,9 @@ class parser(object): params = self.Parse_optional_param_list() self.expect(')') self.NextToken() + self.locallabels = set() body = self.Parse_code_block(typ) + del self.locallabels paramscope = self.scopeindex self.AddSymbol('f', 0, name, Loc=len(self.tree), Type=typ, ParamTypes=params[0], ParamNames=params[1]) @@ -1748,6 +1762,9 @@ class parser(object): # TODO: Enable brackets for list elements e.g. (float)mylist[3], or mylist[5]=4 #self.lazylists = 'lazylists' in options + # Enable use of local labels with duplicate names + self.duplabels = 'duplabels' in options + # Symbol table: # This is a list of all local and global symbol tables. # The first element (0) is the global scope. Each symbol table is a diff --git a/main.py b/main.py index 51a2d51..5adefe7 100644 --- a/main.py +++ b/main.py @@ -14,14 +14,14 @@ Usage: %s [-O [+|-]option[,[+|-]option[,...]]] filename That's an upper case o, not the number zero. If filename is a dash (-) then standard input is used. -Options (+ means default): +Options (+ means active by default, - means inactive by default): extendedglobalexpr + Enables arbitrary expressions in globals (as opposed to dull simple expressions allowed by regular LSL). Needs the optimizer to run for the result to be compilable. extendedtypecast + Allows extended typecast syntax e.g. (string)(integer)a is valid with this option. extendedassignment + Enables &=, |=, ^=, <<=, >>= assignment operators. - explicitcast Add explicit casts where they are implicit. This option + explicitcast - Add explicit casts where they are implicit. This option is useless with 'optimize' and 'optsigns', and is of basically no use in general. allowkeyconcat + Allow string + key and key + string (both return string) @@ -37,12 +37,19 @@ Options (+ means default): optimize + Runs the optimizer. optsigns + Optimize signs in float and integer constants. optfloats + Optimize a float when it is an integral value. - foldtabs Tabs can't be copy-pasted, so they aren't optimized by + foldtabs - Tabs can't be copy-pasted, so they aren't optimized by default. But with support from the viewer, they can be folded too and make it to the uploaded source. This option overrides that check, enabling optimization of strings with tabs. The resulting source isn't guaranteed to be copy-paste-able to a different script, though. + duplabels - Normally, a duplicate label within a function is allowed + by the syntax by using {} blocks; however, the server + will just refuse to save the script (under Mono) or do + something completely unexpected (under LSO: all jumps + will go to the last label with that name). This flag + works around that limitation by replacing the names of + the labels in the output with unique ones. Note that the optimizer doesn't reorder expressions to fold constants. This means that e.g. a + 3 + 5 is not optimized to a + 8; however a + (3 + 5) is. diff --git a/testparser.py b/testparser.py index 96f28a9..c4cceda 100644 --- a/testparser.py +++ b/testparser.py @@ -1,7 +1,7 @@ from lslopt.lslparse import parser,EParseSyntax,EParseUEOF,EParseAlreadyDefined,\ EParseUndefined,EParseTypeMismatch,EParseReturnShouldBeEmpty,EParseReturnIsEmpty,\ EParseInvalidField,EParseFunctionMismatch,EParseDeclarationScope,\ - fieldpos + EParseDuplicateLabel,fieldpos from lslopt.lsloutput import outscript from lslopt.lsloptimizer import optimizer from lslopt import lslfuncs @@ -44,7 +44,7 @@ const string q="\t" parser() -class Test02_Compiler(UnitTestCase): +class Test02_Parser(UnitTestCase): def setUp(self): self.parser = parser() self.outscript = outscript() @@ -171,13 +171,14 @@ class Test02_Compiler(UnitTestCase): self.assertRaises(EParseTypeMismatch, self.parser.parse, '''f(){string i;!i;}''') self.assertRaises(EParseTypeMismatch, self.parser.parse, '''f(){string i;++i;}''') self.assertRaises(EParseTypeMismatch, self.parser.parse, '''g(){integer k;k=g();}''') - self.assertRaises(EParseTypeMismatch, self.parser.parse, '''g(){@x;x;}state x{}''') - self.assertRaises(EParseTypeMismatch, self.parser.parse, '''g(){print(g());}state x{}''') + self.assertRaises(EParseTypeMismatch, self.parser.parse, '''g(){@x;x;}default{}state x{}''') + self.assertRaises(EParseTypeMismatch, self.parser.parse, '''g(){print(g());}default{}state x{}''') self.assertRaises(EParseUndefined, self.parser.parse, '''g(){integer k;k();}''') self.assertRaises(EParseUndefined, self.parser.parse, '''g(){++x;}state x{}''') self.assertRaises(EParseUndefined, self.parser.parse, '''g(){print(x);}state x{}''') self.assertRaises(EParseUEOF, self.parser.parse, '''f(){(integer)''') self.assertRaises(EParseInvalidField, self.parser.parse, '''f(){vector v;v.s;}''') + self.assertRaises(EParseDuplicateLabel, self.parser.parse, 'f(){@x;{@x;}}') self.assertRaises(EParseSyntax, self.parser.parse, '''f(){<1,2,3,4==5>;}''') self.assertRaises(EParseSyntax, self.parser.parse, '''#blah;\ndefault{timer(){}}''') self.assertRaises(EParseTypeMismatch, self.parser.parse, '''f(){<1,2,3,4>"">;}''') @@ -209,11 +210,11 @@ class Test02_Compiler(UnitTestCase): i |= i; "a" "b" "c"; "a"+(key)"b"; (key)"a" + "b"; - i>>=i; + i>>=i; {@x;{@x;jump x;}jump x;} }}''', ['explicitcast','extendedtypecast','extendedassignment', 'extendedglobalexpr', 'allowmultistrings', 'allowkeyconcat', - 'skippreproc'] + 'skippreproc', 'duplabels'] )) print self.parser.scopeindex #self.assertRaises(EParseUnexpected, self.parser.PopScope) @@ -241,6 +242,7 @@ class Test03_Optimizer(UnitTestCase): list L1 = L; list L2 = [1,2,3,4,5,6.0]; list L3 = []; + list L4 = [1,2,3,4,5,6.0,""]+[]; integer RemovesInt = 0; vector AddsVector; vector v=<1,2,f>;