Ticket #38: multiArg-assign-cleanup.patch

File multiArg-assign-cleanup.patch, 16.8 kB (added by hopscc, 2 months ago)
  • Source/SharpGenerator.cobra

     
    28742874                        sw.write('yield return;\n') 
    28752875 
    28762876 
     2877class MultiTargetAssignStatement 
     2878        is partial 
     2879 
     2880        def writeSharpDef(sw as SharpWriter) 
     2881                base.writeSharpDef(sw) 
     2882                _block.writeSharpDef(sw) 
     2883 
    28772884## 
    28782885## Expressions 
    28792886## 
  • Source/Statements.cobra

     
    555555                else 
    556556                        _var = .bindVar(_varExpr) 
    557557                        if _multiArgs 
     558                                _validateMultiArgs 
    558559                                _unpackMultiArgStmts 
    559560                        base._bindImp 
    560561                        _varNumber = .compiler.curBox.makeNextPrivateSerialNumber 
    561562                        _block.bindImp 
    562563                 
     564        def _validateMultiArgs 
     565                n=0 
     566                for id in _multiArgs 
     567                        if not (id inherits IdentifierExpr or id inherits AsExpr) 
     568                                .throwError('Items in comma separated NameId list must be Identifier or Identifier-with-type expression. List item#[n]="[id.token.text]"') 
     569                        n += 1           
     570 
    563571        def _unpackMultiArgStmts 
    564572                """ 
    565573                Handle expansion of statements for multiArg variables in for statements:  
     
    13751383                        # TODO: can there just be "yield return"? 
    13761384                        pass 
    13771385                curCodeMember.hasYieldStmt = true 
     1386                 
     1387class MultiTargetAssignStatement 
     1388        is partial 
     1389        inherits Stmt 
     1390        """ 
     1391        Handle atatement of form  
     1392                a,b[,...] = <List> 
     1393        Gets turned into a sequence of assignExpr. 
     1394                a = <List>[0] 
     1395                b = <LIst>[1] 
     1396 
     1397        TODO: support a,b[,...] = <Enumerable>  
     1398        TODO: support a,b,[,...] = c,d[,...] form 
     1399        """ 
     1400        var _targets as List<of Expr> # Lvalues 
     1401        var _source as Expr             # evaluate to IList/Array/String or otherwise indexable by int 
     1402        var _block as BlockStmt?        # the rewritten/expanded code 
     1403         
     1404        def init(opToken as IToken, args as List<of Expr>, rhs as Expr) 
     1405                base.init(opToken) 
     1406                assert opToken.which == 'ASSIGN' 
     1407                _targets = args 
     1408                _source = rhs 
     1409                 
     1410        def addSubFields 
     1411                base.addSubFields 
     1412                .addField('targets', _targets) 
     1413                .addField('source', _source) 
     1414                .addField('block', _block) 
     1415 
     1416        get targets from var 
     1417 
     1418        get source from var 
     1419 
     1420        get block from var 
     1421 
     1422        def _writeBlock 
     1423                """ 
     1424                Construct expanded assignment block for a multi target assignment. 
     1425                if fewer targets than exprs in _source - extra exprs (silently) ignored 
     1426                if more targets than exprs in _source   - will emit runtime exception 
     1427                        above differ fm python - both give errors 
     1428                Unchecked assumptions;  
     1429                        source is int indexable (x[n]) - c# compiler pick up 
     1430                Assignment block contains 
     1431                        lh_mt_serno = <source> 
     1432                        id0 = lh_mt_serno[0] # id0 is contents of targets[0] 
     1433                        id1 = lh_mt_serno[1] # id1 is contents of targets[1] 
     1434                                (... repeated for number of items in targets specifying ids (lvalues)) 
     1435                """ 
     1436                stmts = List<of Stmt>() 
     1437                assignTkn = .lastToken  # the one we stored in init 
     1438                serNo = .compiler.curBox.makeNextPrivateSerialNumber 
     1439                tmpName = 'lh_mt_[serNo]'  # lh=local handler, mt = multiTarget 
     1440                ttoken = .lastToken.copy('XXX', 'xxx')  # invalid template token for copying 
     1441                idToken = ttoken.copy('ID', tmpName) 
     1442                 
     1443                tmpId = IdentifierExpr(idToken, idToken.text) 
     1444                s = AssignExpr(assignTkn, assignTkn.which, tmpId, _source)  
     1445                stmts.add(s)   
     1446 
     1447                count = 0 
     1448                for id in _targets 
     1449                        intToken = ttoken.copy('INTEGER_LIT', '[count]') 
     1450                        intToken.value = count 
     1451                        countIntLit = IntegerLit(intToken) 
     1452                        exprs = List<of Expr>() 
     1453                        exprs.add(countIntLit) 
     1454                        tmpIdn = IdentifierExpr(ttoken.copy('ID', tmpName), tmpName) 
     1455                        idxExpr = IndexExpr(ttoken.copy('LBRACKET', r'['), tmpIdn, exprs) 
     1456                        stmts.add(AssignExpr(assignTkn, assignTkn.which, id, idxExpr)) 
     1457                        count += 1 
     1458                _block = BlockStmt(ttoken.copy('INDENT', ''), stmts)     
     1459                 
     1460        #Push up to baseclass or as Utility method ? 
     1461        #TODO: make a better test for real LValue-ness 
     1462        def _isLValue( id as Expr) as bool 
     1463                return (id inherits     IdentifierExpr) or (id inherits DotExpr) or (id inherits IndexExpr) 
     1464                 
     1465        def _bindImp 
     1466                base._bindImp 
     1467                for id in _targets 
     1468                        if not _isLValue(id) 
     1469                                .throwError('"[id.token.text]" must be an assignable lvalue (Identifier, Member or Index Expression)') 
     1470                #TODO: chk _source supports an int indexer ?? 
     1471                _writeBlock 
     1472                _block.bindImp 
     1473                 
  • Source/CobraParser.cobra

     
    225225                                .recordError(error.token, error.message) 
    226226 
    227227                _nextTokenIndex = 0 
    228  
    229  
     228                 
    230229        ## 
    231230        ## Tokens 
    232231        ## 
     
    18831882                                else 
    18841883                                        if .looksLikeType(0) and .looksLikeVarNameIsNext(1) 
    18851884                                                .throwError('The correct local variable syntax is "name as Type" or "name = initValue". Try "[.peek(1).text] as [.peek(0).text]."') 
    1886                                         if .looksLikeMultiArgIsNext 
    1887                                                 s = .multiArgAssign  
    1888                                         else 
    1889                                                 s = .expression 
    1890                                                 s.afterParserRecognizesStatement 
    1891                                                          
     1885                                        s = .expression 
     1886                                        s.afterParserRecognizesStatement 
     1887                                        if .optional('COMMA') 
     1888                                                s = .multiTargetAssign(s to Expr) 
    18921889                if expectEOL 
    18931890                        if .verbosity>=5 
    18941891                                print '<> last statement start token=[token]' 
     
    19921989                """ 
    19931990                token = .expect('FOR') 
    19941991                varr = .nameExpr 
    1995                 if .optional('COMMA')     # forStmt multiarg    for v1,...  
    1996                         args = .commaSepNameIds(['IN' ,'EOL']) 
     1992                if .optional('COMMA')     # forStmt multiarg    for v1,... in  
     1993                        args = .commaSepExprsPartial(['IN' ,'EOL'], 'IN') 
    19971994                        if .last.which <> 'IN' 
    19981995                                .throwError("Comma separated nameId list in forStatement needs to terminate with an IN token ('in')") 
    19991996                        if args.count == 0  # "for v1, in ..."  single multiarg variable  
     
    27122709                                        throw FallThroughException() 
    27132710                                else 
    27142711                                        throw 
    2715  
    2716         def multiArgAssign as Stmt 
    2717                 # id, [id,]... = <expr> 
    2718                 args = .commaSepIds(['ASSIGN', 'EOL']) 
     2712  
     2713        def multiTargetAssign(arg0 as Expr) as Stmt 
     2714                # id, |[id,]... = <expr> 
     2715                args = .commaSepExprsPartial(['ASSIGN', 'EOL'], 'ASSIGN') 
     2716                args.insert(0, arg0) 
    27192717                if .last.which <> 'ASSIGN' 
    27202718                        .throwError('Comma separated identifier list needs to end with an assignment ("=").') 
    2721                 assignTkn = .last 
     2719                assignTkn = .last to ! 
    27222720                rhs = .expression 
    2723                 return .multiArgBlock(assignTkn, args, rhs) 
    2724          
    2725         def multiArgBlock(assignTkn as IToken?, args as List<of Expr>, rhs as Expr) as Stmt 
    2726                 """ 
    2727                 Construct expanded assignment block for a multi arg assignment. 
    2728                 if less args than exprs in rhs - extra exprs (silently) ignored 
    2729                 if more args than exprs in rhs  - additional args are (silently) ignored (not changed) 
    2730                         above differ fm python - both give errors 
    2731                 Unchecked assumptions;  
    2732                         rhs is indexable (x[n] == IList) - c# compiler pick up 
    2733                 Assignment block contains 
    2734                         try 
    2735                                 tmpId = <rhsExpr> 
    2736                                 id0 = tmpId[0] # id is contents of args[0] 
    2737                                 id1 = tmpId[1] # id is contents of args[0] 
    2738                                         (... repeated for number of items in args specifying ids) 
    2739                         catch ArgumentOutOfRangeException 
    2740                                 pass     
    2741                 """ 
    2742                 stmts = List<of Stmt>() 
    2743                 tmpName = 'lh_ma_[rhs.serialNum]' 
    2744                 token = assignTkn.copy('XXX', 'xxx')  # invalid token template for copying 
    2745                 idToken = token.copy('ID', tmpName) 
     2721                return MultiTargetAssignStatement(assignTkn, args, rhs) 
    27462722                 
    2747                 tmpId = IdentifierExpr(idToken, idToken.text) 
    2748                 s = AssignExpr(assignTkn, assignTkn.which, tmpId, rhs)  
    2749                 stmts.add(s)   
    2750  
    2751                 count = 0 
    2752                 for id in args 
    2753                         intToken = token.copy('INTEGER_LIT', '[count]') 
    2754                         intToken.value = count 
    2755                         countIntLit = IntegerLit(intToken) 
    2756                         exprs = List<of Expr>() 
    2757                         exprs.add(countIntLit) 
    2758                         tmpIdn = IdentifierExpr(token.copy('ID', tmpName), tmpName) 
    2759                         idxExpr = IndexExpr(token.copy('LBRACKET', r'['), tmpIdn, exprs) 
    2760                         stmts.add(AssignExpr(assignTkn, assignTkn.which, id , idxExpr)) 
    2761                         count += 1 
    2762                 return BlockStmt(token.copy('INDENT', ''), stmts)        
    2763  
    27642723        def callExpr as Expr 
    27652724                """ 
    27662725                Syntax: 
     
    28122771                expr.argumentLabel = label 
    28132772                return expr 
    28142773 
     2774        def commaSepExprsPartial(terminators as IList<of String>, binOpBrk as String) as List<of Expr> 
     2775                # As commaSepExprs but setup to break out of middle of binOpExpression on terminating token 
     2776                assert _binaryOpPrec.containsKey(binOpBrk) 
     2777                realPrec = _binaryOpPrec[binOpBrk] 
     2778                _binaryOpPrec[binOpBrk] = -1    # reset precedence to exit expression parser when hit this op 
     2779                try 
     2780                        exprs = .commaSepExprs(terminators) 
     2781                finally 
     2782                        _binaryOpPrec[binOpBrk] = realPrec 
     2783                return exprs 
     2784                         
    28152785        def commaSepExprs(terminator as String) as List<of Expr> 
    28162786                return .commaSepExprs([terminator], false, false) 
    28172787 
     
    28632833                        expectSep = true 
    28642834                return exprs 
    28652835                 
    2866         def commaSepNameIds(terminators as IList<of String>) as List<of Expr> 
    2867                 return _commaSepLValues(terminators, true) 
    2868  
    2869         def      commaSepIds(terminators as IList<of String>) as List<of Expr> 
    2870                 return _commaSepLValues(terminators, false) 
    2871                          
    2872         def _commaSepLValues(terminators as IList<of String>, expectingNameIds as bool ) as List<of Expr> 
    2873                 """ 
    2874                 pickup comma sep stream of Identifiers or NameIds  
    2875                 Example source 
    2876                         ... idexpr, TERMINATOR 
    2877                         ... idexpr, idexpr ...  TERMINATOR 
    2878                         ... idexpr, idexpr, ... TERMINATOR 
    2879                 Returns 
    2880                         A list of Identifier Expressions ([DotExpr] IdentifiersExpr) or NameIds  
    2881                 """ 
    2882                 expectSep = false 
    2883                 sep = 'COMMA' 
    2884                 exprs = List<of Expr>() 
    2885                 while true 
    2886                         if .peek.which in terminators 
    2887                                 .grab 
    2888                                 break 
    2889                         if expectSep 
    2890                                 .expect(sep) 
    2891                         if .peek.which in terminators 
    2892                                 .grab 
    2893                                 break 
    2894                         .newOpStack 
    2895                         try 
    2896                                 if expectingNameIds 
    2897                                         exprs.add(.nameExpr) 
    2898                                 else 
    2899                                         exprs.add(.idExpression(0,nil)) 
    2900                         finally 
    2901                                 .delOpStack 
    2902                         expectSep = true 
    2903                 return exprs 
    2904  
    2905         def idExpression(precedence as int, left as Expr?) as Expr 
    2906                 # Expression tree walker for Identifier or DotExpr Identifier Stream  
    2907                 # This method and next are simplified versions of expression and expression2 
    2908                 # this code has to deal with scanning for tokens fo an expression that may be an identifier  
    2909                 # (and to stop on anything else) 
    2910                 if left is nil 
    2911                         left = .idExpression2 
    2912                 while true 
    2913                         peek = .peek.which 
    2914                         # get precedence (and detect non-binary operators) 
    2915                         binaryOpPrec = Utils.getSI(_binaryOpPrec, peek, -1) 
    2916                         if peek <> 'DOT'    # DOT is the only binary op allowed in Id stream 
    2917                                 break    
    2918  
    2919                         opToken = .grab 
    2920                         op = opToken.which 
    2921                         assert _binaryOpPrec.containsKey(op) 
    2922                         _leftStack.push(left to !) 
    2923                         .opStack.push(op) 
    2924                         try 
    2925                                 # get the right hand side of a binary operator expression 
    2926                                 prec = if(OperatorSpecs.rightAssoc.containsKey(op), binaryOpPrec, binaryOpPrec+1) 
    2927                                 if op == 'DOT' and .peek and .peek.isKeyword 
    2928                                         # support foo.bar where bar is a keyword. Ex: foo.this 
    2929                                         right = MemberExpr(.grab) to Expr 
    2930                                 else 
    2931                                         right = .idExpression(prec, nil) 
    2932                                         left = DotExpr(opToken, op, left, right, false) 
    2933                         finally 
    2934                                 .opStack.pop 
    2935                                 _leftStack.pop 
    2936                 assert left 
    2937                 return left to ! 
    2938                  
    2939         def idExpression2 as Expr 
    2940                 peekToken = .peek 
    2941                 peek = peekToken.which 
    2942                 if peek=='DOT'          # leading dot 
    2943                         token = .grab 
    2944                         .opStack.push('DOT') 
    2945                         try 
    2946                                 peekToken = .peek 
    2947                                 peek = peekToken.which 
    2948                                 if peek=='ID' or peekToken.isKeyword 
    2949                                         memberToken = .idOrKeyword 
    2950                                         expr = MemberExpr(memberToken) to Expr 
    2951                                 else 
    2952                                         .throwError('Syntax error after "." Expecting variable Id ') 
    2953                         finally 
    2954                                 .opStack.pop 
    2955                         return BinaryOpExpr.make(token to !, 'DOT', ThisLit(token), expr) 
    2956                 else if peek=='ID' 
    2957                         return .identifierExpr 
    2958                 else if .opStack.count and .opStack.peek=='DOT' and .peek.isKeyword 
    2959                         return .identifierExpr 
    2960                          
    2961                 msg = 'Expecting an Id expression.' 
    2962                 if peekToken.isKeyword 
    2963                         msg += ' "[peekToken.text]" is a reserved keyword that is not expected here.' 
    2964                 .throwError(msg) 
    2965                 assert false 
    2966                 return ThisLit(token)  # To shut c# compiler up re paths without return values - notreached 
    2967                  
    29682836        def forExpr as ForExpr 
    29692837                """ 
    29702838                t = for x in stuff where x<0 get x*x 
     
    34383306                token = .peek(peekAhead) 
    34393307                if token.which == 'ID' 
    34403308                        return Utils.startsNonLower(token.value to String) 
    3441                 if token.isKeyword and token.text in ['bool', 'char', 'decimal', 'int', 'uint', 'float', 'number'] 
    3442                         return true 
    3443                 return false 
     3309                return .isOneOfKeywords(token, ['bool', 'char', 'decimal', 'int', 'uint', 'float', 'number']) 
    34443310 
    34453311        def looksLikeVarNameIsNext(peekAhead as int) as bool 
    34463312                token = .peek(peekAhead) 
    34473313                return token is not nil and token.which=='ID' and Utils.startsWithLowerLetter(token.text) 
    3448  
    3449         def looksLikeMultiArgIsNext as bool 
    3450                 """ 
    3451                 peek ahead for ID (local or instance variable) COMMA or  
    3452                 {DOT, ID}... (memberVar) COMMA 
    3453                 """ 
    3454                 state = 0 
    3455                 peekAhead =0 
    3456                 match = false 
    3457                 while not match 
    3458                         token = .peek(peekAhead) 
    3459                         if token is nil 
    3460                                 break 
    3461                         if token.which == 'DOT' 
    3462                                 state=1 
    3463                         else if token.which == 'ID' 
    3464                                 state = 2 
    3465                         else if token.which == 'COMMA'  
    3466                                 match = state == 2 # comma after [.]ID 
    3467                         else 
    3468                                 break 
    3469                         peekAhead += 1 
    3470                 return match 
    3471  
     3314                 
     3315        def isOneOfKeywords(nToken as IToken?, keywords as List<of String>) as bool 
     3316                token = nToken to ! 
     3317                return token.isKeyword and token.text in keywords 
     3318                 
    34723319        def recordError(msg as String) as ParserException 
    34733320                return .recordError(.last, msg) 
    34743321 
  • Tests/820-errors/300-statements/500-multi-assign.cobra

     
     1# compile failure cases for multiTarget assignment 
     2class MAssign 
     3        def main is shared 
     4                # missing list terminators (ASSIGN) 
     5                a,b,c == [1,2,3]        # .error. Comma separated identifier list needs to end with an assignment 
  • Tests/320-misc-two/810-multi-assignment/110-multi-assign.cobra

     
    99         
    1010        pro p0 from _i0 
    1111        pro p1 from _i1 
    12                  
     12 
     13        set [i as int] 
     14                branch i  
     15                        on 0 
     16                                .x0 = value 
     17                        on 1 
     18                                .x1 = value 
     19                        else 
     20                                throw ArgumentOutOfRangeException('[i] out of range 0,1') 
     21                         
    1322        def main is shared 
    1423                ma = MultiAssign() 
    1524                ma.nonLocalVar 
    1625                ma.forIEnum 
     26                ma.forIndexor 
    1727                ma.forSwap 
    1828 
    1929        def nonLocalVar 
     
    4757                assert a == 1 
    4858                assert b == 2 
    4959                 
     60        def forIndexor 
     61                this[0] = 'x' 
     62                this[1] = 'y' 
     63                assert .x0 == 'x' 
     64                assert .x1 == 'y' 
     65 
     66                this[0],this[1] = ['aa', 'bb'] 
     67                assert .x0 == 'aa' 
     68                assert .x1 == 'bb' 
     69         
     70                tx=this 
     71                tx[0],tx[1] = ['xa', 'xb'] 
     72                assert .x0 == 'xa' 
     73                assert .x1 == 'xb' 
     74                 
    5075        def forSwap 
    5176                # common variable swap idiom ( python a,b = b,a) 
    5277                a = 99 
     
    5479                a, b = [b, a] 
    5580                assert a == 100 
    5681                assert b == 99 
     82                 
  • Tests/320-misc-two/810-multi-assignment/120-multi-assign-fail.cobra

     
     1# test failure cases for multiTarget assignment 
     2class MAssign 
     3        def main is shared 
     4                # non LValues 
     5                a,b,c = [1,2,3]  
     6                assert a == 1 
     7                assert b == 2 
     8                assert c == 3 
     9                 
     10                33,a,b = [1,2,3]        #.error. "33" must be an assignable lvalue 
     11                a,33,c = [1,2,3]        #.error. "33" must be an assignable lvalue 
     12                a,b,34 = [1,2,3]        #.error. "34" must be an assignable lvalue 
     13 
     14                'aye',b,c = [1,2,3]     #.error. "'aye'" must be an assignable lvalue 
     15                a,'bee',c = [1,2,3]     #.error. "'bee'" must be an assignable lvalue 
     16                a,b,"cee" = [1,2,3]     #.error. ""cee"" must be an assignable lvalue 
     17         
     18                # missing list terminators (ASSIGN) 
     19#               a,b,c == [1,2,3]        # .compile-error . Comma separated identifier list needs to end with an assignment 
  • Tests/320-misc-two/810-multi-assignment/210-multi-arg-for-list-fail.cobra

     
    44 
    55        def main is shared 
    66                ls = [1, 2, 3, 4, 5, 6] 
     7                # ls needs to be pairs i.e [[1,2],[3,4],[5,6]] 
    78                for l1, l2 in ls        # .error. Cannot find an indexer 
    89                        assert l1 in [1, 3, 5] 
    910                        assert l2 in [2, 4, 6] 
     11                         
     12                for a, 2 in [1,2]        #.error. must be Identifier or Identifier-with-type 
     13                        pass 
     14                         
     15                for a, b, 'xx' in [1,2] #.error. must be Identifier or Identifier-with-type expression. List item#2 
     16                        pass 
     17