Wiki

Ticket #38: multiArg-assign-cleanup.patch

File multiArg-assign-cleanup.patch, 16.8 KB (added by hopscc, 16 years 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