Ticket #38: multiArg-comma-sep.patch

File multiArg-comma-sep.patch, 11.4 kB (added by hopscc, 2 months ago)
  • Source/Statements.cobra

     
    13831383                        # TODO: can there just be "yield return"? 
    13841384                        pass 
    13851385                curCodeMember.hasYieldStmt = true 
    1386  
    1387  
     1386                 
    13881387class MultiTargetAssignStatement 
    13891388        is partial 
    13901389        inherits Stmt 
    13911390        """ 
    1392         Handle atatement of form  
    1393                 a, b[,...] = <List> 
    1394         Gets turned into a sequence of assignExpr. 
    1395                 a = <List>[0] 
    1396                 b = <List>[1] 
    1397  
     1391        Handle statements of form  
     1392                a,[b,...] = <List-Generating-Expr> 
     1393        or 
     1394                a,[b,...] = <expr>,[<expr>,...] 
     1395        Gets turned into various sequences of assignExpr. 
     1396                tmp = <List-Generating-Expr> 
     1397                a = tmp[0] 
     1398                b = tmp[1] 
     1399                ... 
     1400                a = <expr> 
     1401                b = <expr> 
     1402                ... 
    13981403        TODO: support a, b[,...] = <Enumerable>  
    13991404        TODO: support a, b[,...] = <KeyValuePair|DictionaryEntry> 
    1400         TODO: support a, b[,...] = c,d[,...] form 
    14011405        """ 
     1406 
    14021407        var _targets as List<of Expr> # Lvalues 
    1403         var _source as Expr             # evaluate to IList/Array/String or otherwise indexable by int 
    1404         var _block as BlockStmt?        # the rewritten/expanded code 
     1408        var _source as Expr?          # evaluate to IList/Array/String or otherwise indexable by int 
     1409        var _rvals as List<of Expr>?  # list of source expressions 
     1410        var _block as BlockStmt?      # the rewritten/expanded code 
    14051411         
    1406         def init(opToken as IToken, args as List<of Expr>, rhs as Expr) 
     1412        def init(opToken as IToken, args as List<of Expr>, rhs as Expr?, rhsList as List<of Expr>?) 
    14071413                base.init(opToken) 
    14081414                assert opToken.which == 'ASSIGN' 
     1415                assert (rhs or rhsList) and not (rhs and rhsList) # either but not both 
    14091416                _targets = args 
    14101417                _source = rhs 
     1418                _rvals = rhsList 
    14111419                 
    14121420        def addSubFields 
    14131421                base.addSubFields 
    14141422                .addField('targets', _targets) 
    14151423                .addField('source', _source) 
     1424                .addField('rvals', _rvals) 
    14161425                .addField('block', _block) 
    14171426 
    14181427        get targets from var 
    14191428 
    14201429        get source from var 
    14211430 
     1431        get rvals from var 
     1432 
    14221433        get block from var 
    14231434 
    1424         def _writeBlock 
     1435        def _bindImp 
     1436                assert _source or _rvals 
     1437                base._bindImp 
     1438                for target in _targets 
     1439                        if not _isLValue(target) 
     1440                                .throwError('"[target.toCobraSource]" is not an assignable lvalue (identifier, var, property or indexer).') 
     1441                if _rvals 
     1442                        if _targets.count <> _rvals.count 
     1443                                .throwError('The number of targets ([_targets.count]) must be the same as the number of expressions ([_rvals.count]) assigned to them') 
     1444                        #If rvals are all safe to assign (no rerefs) we can do the simplest thing and turn  
     1445                        # them into a sequence of individual assignments.       i.e a,b = 1,2  or a,b = c,d   cases 
     1446                        # If not, we need to turn the rvals list into a literal list, pinning the current values, 
     1447                        # and do the expansion on the list as a single item  i.e  a,b = b,a -> a,b = [b,a]  
     1448                        if _isSafeToSimpleAssign() 
     1449                                _writeBlockForList 
     1450                        else 
     1451                                _source = ListLit(.lastToken.copy('LBRACKET', r'['), _rvals) 
     1452                if _source 
     1453                        #TODO: chk _source supports an int indexer  somehow... 
     1454                        #if not .can_be_indexed_by_int_indexer(_source)  
     1455                        #       .throwError(r"rhs expression [_source.toCobraSource] must support being indexed by an integer offset (e.g rhs[0]") 
     1456                        _writeBlockForItem 
     1457                                 
     1458                _block.bindImp 
     1459                 
     1460        def _isLValue(id as Expr) as bool 
     1461                # TODO: push to Expr.isLValue. probably require .didBindImp 
     1462                # TODO: for DotExpr and IndexExpr check if there is a setter 
     1463                # TODO: for MemberExpr check that its a setter property or visible var (not a method) 
     1464                return id inherits IdentifierExpr or id inherits DotExpr or id inherits IndexExpr 
     1465                 
     1466        def _isSafeToSimpleAssign as bool 
     1467                # conservatively we just presume its safe if the rvals list is all Literals 
     1468                if all for e in _rvals get e inherits Literal 
     1469                        return true  
     1470                # or if identifiers in rvals are not also in target 
     1471                if _disjointIdentifierLists()  
     1472                        return true 
     1473                # There are possibly others but above expected to be most common usage   
     1474                # TODO: expand this for more wide ranging simple assignment e.g MemberExprs non matching 
     1475                return false 
     1476 
     1477        def _disjointIdentifierLists as bool 
     1478                # Specifically all of targets list are IdentifierExprs and rvals are Literals, DotExprs,  
     1479                # MemberExprs or Indentifiers and any Identifiers are not also in target list 
     1480                tnames = Set<of String>() 
     1481                for t in _targets 
     1482                        if t inherits IdentifierExpr or t inherits AsExpr 
     1483                                tnames.add((t to NameExpr).name)         
     1484                        else 
     1485                                return false 
     1486                if all for e in _rvals get e inherits Literal or e inherits DotExpr or e inherits MemberExpr or _ 
     1487                                (e inherits IdentifierExpr and (e to IdentifierExpr).name not in tnames)  
     1488                        return true      
     1489                return false 
     1490                 
     1491        def _writeBlockForList 
    14251492                """ 
    1426                 Construct expanded assignment block for a multi target assignment. 
     1493                Construct expanded assignment block for a multi target assignment from a list of expressions. 
     1494                Assignment block contains 
     1495                        id0 = rvals[0] # id0 is contents of targets[0] 
     1496                        id1 = rvals[1] # id1 is contents of targets[1] 
     1497                                (... repeated for number of items in targets specifying ids (lvalues)) 
     1498                """ 
     1499                assert _targets.count == _rvals.count 
     1500                stmts = List<of Stmt>() 
     1501                assignTkn = .lastToken  # the one we stored in init 
     1502                count = 0 
     1503                for id in _targets 
     1504                        expr = _rvals[count] 
     1505                        stmts.add(AssignExpr(assignTkn, assignTkn.which, id, expr)) 
     1506                        count += 1 
     1507                _block = BlockStmt(.lastToken.copy('INDENT', ''), stmts)         
     1508 
     1509        def _writeBlockForItem 
     1510                """ 
     1511                Construct expanded assignment block for a multi target assignment from a single source expression. 
    14271512                if fewer targets than exprs in _source - extra exprs (silently) ignored 
    14281513                TODO: give error when rvalues are too many 
    14291514                if more targets than exprs in _source   - will emit runtime exception 
    14301515                        above differ fm python - both give errors 
    14311516                Unchecked assumptions;  
    1432                         source is int indexable (x[n]) - c# compiler pick up 
     1517                        source is int indexable (x[n]) - c# compiler pick up currently 
    14331518                Assignment block contains 
    14341519                        lh_mt_serno = <source> 
    14351520                        id0 = lh_mt_serno[0] # id0 is contents of targets[0] 
     
    14591544                        stmts.add(AssignExpr(assignTkn, assignTkn.which, id, idxExpr)) 
    14601545                        count += 1 
    14611546                _block = BlockStmt(ttoken.copy('INDENT', ''), stmts)     
    1462  
    1463         def _isLValue(id as Expr) as bool 
    1464                 # TODO: push to Expr.isLValue. probably require .didBindImp 
    1465                 # TODO: for DotExpr and IndexExpr check if there is a setter 
    1466                 # TODO: for MemberExpr check that its a setter property or visible var (not a method) 
    1467                 return id inherits IdentifierExpr or id inherits DotExpr or id inherits IndexExpr 
    1468  
    1469         def _bindImp 
    1470                 base._bindImp 
    1471                 for target in _targets 
    1472                         if not _isLValue(target) 
    1473                                 .throwError('"[target.toCobraSource]" is not an assignable lvalue (identifier, var, property or indexer).') 
    1474                 # TODO: check _source supports an int indexer ?? 
    1475                 _writeBlock 
    1476                 _block.bindImp 
  • Source/CobraParser.cobra

     
    27182718                if .last.which <> 'ASSIGN' 
    27192719                        .throwError('Comma-separated assignment targets must end with "=", or this is a general syntax error.') 
    27202720                assignTkn = .last to ! 
    2721                 rhs = .expression 
    2722                 return MultiTargetAssignStatement(assignTkn, args, rhs) 
     2721                rhs as Expr? = .expression 
     2722                if .optional('COMMA') 
     2723                        rhsList = .commaSepExprs('EOL') 
     2724                        assert .last.which=='EOL' 
     2725                        .undo  # need EOL 
     2726                        rhsList.insert(0, rhs) 
     2727                        rhs = nil 
     2728                return MultiTargetAssignStatement(assignTkn, args, rhs, rhsList) 
    27232729                 
    27242730        def callExpr as Expr 
    27252731                """ 
  • Tests/320-misc-two/810-multi-assignment/300-multiAssign-comma-sep.cobra

     
     1# tests for multiTarget assignment from commasep expression stream 
     2class MAssign 
     3         
     4        var avar = 99 
     5        var bvar = 88 
     6        var _avar1 = 77 
     7 
     8        get prop 
     9                return 66 
     10         
     11        def main is shared 
     12                ma = MAssign() 
     13                ma.literals 
     14                ma.variables 
     15                ma.exprs 
     16                ma.asExpected 
     17                ma.swap 
     18                 
     19        def literals     
     20                a,b,c = 1,2,3 
     21                assert a==1 
     22                assert b==2 
     23                assert c==3 
     24         
     25                x,y,z = 'this','is','silly' 
     26                assert x == 'this' 
     27                assert y == 'is' 
     28                assert z == 'silly' 
     29         
     30                x,y = 'a','b' 
     31                assert x == 'a' 
     32                assert y == 'b' 
     33                assert z == 'silly' 
     34                 
     35                #mixed types 
     36                a,x,ch = 99,'wokka',c'x' 
     37                assert a == 99 
     38                assert x == 'wokka' 
     39                assert ch == c'x' 
     40                 
     41        def variables 
     42                a = 22 
     43                b = 47 
     44                c = 76 
     45                 
     46                d,e = a,b 
     47                assert d == 22 
     48                assert e == 47 
     49         
     50                d,e = .avar, .bvar 
     51                assert d == 99 
     52                assert e == 88 
     53         
     54                d,e = _avar1, 10 
     55                assert d == 77 
     56                assert e == 10 
     57         
     58                d,e = -1, _avar1 
     59                assert d == -1 
     60                assert e == 77 
     61                 
     62                d,c = .prop, a 
     63                assert d == 66 
     64                assert c == 22 
     65                 
     66        def exprs 
     67                a,b = 99+1,100-99 
     68                assert a == 100 
     69                assert b == 1 
     70                 
     71                a = .geti(0)    # OK 
     72                assert a ==2 
     73 
     74                a, = .geti(1),   # OK - note trailing commas 
     75                assert a == 3 
     76         
     77                #a, = .geti(1)   # typo missing trailing ',': peculiar error: cannot find an indexer 
     78                #assert a == 3 
     79                 
     80                a,b = .geti(2), .geti(10) 
     81                assert a == 4 
     82                assert b == 12 
     83         
     84                a,b = 0,0 
     85                a,b = .geti(2), .geti(10) 
     86                assert a == 4 
     87                assert b == 12 
     88                 
     89        def geti(i as int) as int 
     90                return i+2 
     91 
     92        def asExpected 
     93                # test expression values are pinned outside assignment stream 
     94                a,b = c=2,3 
     95                assert a == 2  
     96                assert c == 2 
     97                assert b == 3 
     98                 
     99                a=100 
     100                a,b = a+1, a+2 
     101                assert a == 101 
     102                assert b == 102 
     103         
     104                c,c = 0,1 
     105                assert c == 1 
     106                 
     107        def swap 
     108                a = 10 
     109                b = 20 
     110                assert a == 10 
     111                assert b == 20 
     112                 
     113                a,b = b,a 
     114                assert a == 20 
     115                assert b == 10 
     116                 
     117                 
     118                 
     119                 
  • Tests/320-misc-two/810-multi-assignment/310-multiAssign-comma-sep-fail.cobra

     
     1# failure cases  for multiTarget assignment from commasep expression stream 
     2class MAssign 
     3        def main is shared 
     4         
     5                a,b = 1,2,3 #.error. number of targets (2) must be the same as the number of expressions (3) 
     6 
     7                a,b,c = 1,2 #.error. number of targets (3) must be the same as the number of expressions (2) 
  • Developer/IntermediateReleaseNotes.text

     
    1717.code 
    1818    %% error 'Cannot build this file till mondo-patch installed' 
    1919     
    20 * Added support for "multi-arg assignment" which allows you to assign to a number of variables from contents of a list in one statement 
     20* Added support for "multi-arg assignment" which allows you to assign to a number of variables from a compatible list of expressions in one statement 
    2121.code 
     22        a,b,c = 'val1', 999, c'\t' 
     23        d,e,f = 99+1, 100*1, 101-1 
     24        a,b = b,a   # one line swap 
     25or from contents of a list or array (any integer indexable item) in one statement 
     26.code 
    2227        a,b,c = ['val1', 'val2', 'val3'] 
     28        d,e,f = @[99, 100, 101] 
    2329Also support such assignment in for statements for (generic) dictionaries and lists 
    2430.code 
    2531    for key, value in aDictionary