Ticket #38: multiArg-assign-cleanup.patch
File multiArg-assign-cleanup.patch, 16.8 KB (added by hopscc, 16 years ago) |
---|
-
Source/SharpGenerator.cobra
2874 2874 sw.write('yield return;\n') 2875 2875 2876 2876 2877 class MultiTargetAssignStatement 2878 is partial 2879 2880 def writeSharpDef(sw as SharpWriter) 2881 base.writeSharpDef(sw) 2882 _block.writeSharpDef(sw) 2883 2877 2884 ## 2878 2885 ## Expressions 2879 2886 ## -
Source/Statements.cobra
555 555 else 556 556 _var = .bindVar(_varExpr) 557 557 if _multiArgs 558 _validateMultiArgs 558 559 _unpackMultiArgStmts 559 560 base._bindImp 560 561 _varNumber = .compiler.curBox.makeNextPrivateSerialNumber 561 562 _block.bindImp 562 563 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 563 571 def _unpackMultiArgStmts 564 572 """ 565 573 Handle expansion of statements for multiArg variables in for statements: … … 1375 1383 # TODO: can there just be "yield return"? 1376 1384 pass 1377 1385 curCodeMember.hasYieldStmt = true 1386 1387 class 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
225 225 .recordError(error.token, error.message) 226 226 227 227 _nextTokenIndex = 0 228 229 228 230 229 ## 231 230 ## Tokens 232 231 ## … … 1883 1882 else 1884 1883 if .looksLikeType(0) and .looksLikeVarNameIsNext(1) 1885 1884 .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) 1892 1889 if expectEOL 1893 1890 if .verbosity>=5 1894 1891 print '<> last statement start token=[token]' … … 1992 1989 """ 1993 1990 token = .expect('FOR') 1994 1991 varr = .nameExpr 1995 if .optional('COMMA') # forStmt multiarg for v1,... 1996 args = .commaSep NameIds(['IN' ,'EOL'])1992 if .optional('COMMA') # forStmt multiarg for v1,... in 1993 args = .commaSepExprsPartial(['IN' ,'EOL'], 'IN') 1997 1994 if .last.which <> 'IN' 1998 1995 .throwError("Comma separated nameId list in forStatement needs to terminate with an IN token ('in')") 1999 1996 if args.count == 0 # "for v1, in ..." single multiarg variable … … 2712 2709 throw FallThroughException() 2713 2710 else 2714 2711 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) 2719 2717 if .last.which <> 'ASSIGN' 2720 2718 .throwError('Comma separated identifier list needs to end with an assignment ("=").') 2721 assignTkn = .last 2719 assignTkn = .last to ! 2722 2720 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) 2746 2722 2747 tmpId = IdentifierExpr(idToken, idToken.text)2748 s = AssignExpr(assignTkn, assignTkn.which, tmpId, rhs)2749 stmts.add(s)2750 2751 count = 02752 for id in args2753 intToken = token.copy('INTEGER_LIT', '[count]')2754 intToken.value = count2755 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 += 12762 return BlockStmt(token.copy('INDENT', ''), stmts)2763 2764 2723 def callExpr as Expr 2765 2724 """ 2766 2725 Syntax: … … 2812 2771 expr.argumentLabel = label 2813 2772 return expr 2814 2773 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 2815 2785 def commaSepExprs(terminator as String) as List<of Expr> 2816 2786 return .commaSepExprs([terminator], false, false) 2817 2787 … … 2863 2833 expectSep = true 2864 2834 return exprs 2865 2835 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 NameIds2875 Example source2876 ... idexpr, TERMINATOR2877 ... idexpr, idexpr ... TERMINATOR2878 ... idexpr, idexpr, ... TERMINATOR2879 Returns2880 A list of Identifier Expressions ([DotExpr] IdentifiersExpr) or NameIds2881 """2882 expectSep = false2883 sep = 'COMMA'2884 exprs = List<of Expr>()2885 while true2886 if .peek.which in terminators2887 .grab2888 break2889 if expectSep2890 .expect(sep)2891 if .peek.which in terminators2892 .grab2893 break2894 .newOpStack2895 try2896 if expectingNameIds2897 exprs.add(.nameExpr)2898 else2899 exprs.add(.idExpression(0,nil))2900 finally2901 .delOpStack2902 expectSep = true2903 return exprs2904 2905 def idExpression(precedence as int, left as Expr?) as Expr2906 # Expression tree walker for Identifier or DotExpr Identifier Stream2907 # This method and next are simplified versions of expression and expression22908 # this code has to deal with scanning for tokens fo an expression that may be an identifier2909 # (and to stop on anything else)2910 if left is nil2911 left = .idExpression22912 while true2913 peek = .peek.which2914 # 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 stream2917 break2918 2919 opToken = .grab2920 op = opToken.which2921 assert _binaryOpPrec.containsKey(op)2922 _leftStack.push(left to !)2923 .opStack.push(op)2924 try2925 # get the right hand side of a binary operator expression2926 prec = if(OperatorSpecs.rightAssoc.containsKey(op), binaryOpPrec, binaryOpPrec+1)2927 if op == 'DOT' and .peek and .peek.isKeyword2928 # support foo.bar where bar is a keyword. Ex: foo.this2929 right = MemberExpr(.grab) to Expr2930 else2931 right = .idExpression(prec, nil)2932 left = DotExpr(opToken, op, left, right, false)2933 finally2934 .opStack.pop2935 _leftStack.pop2936 assert left2937 return left to !2938 2939 def idExpression2 as Expr2940 peekToken = .peek2941 peek = peekToken.which2942 if peek=='DOT' # leading dot2943 token = .grab2944 .opStack.push('DOT')2945 try2946 peekToken = .peek2947 peek = peekToken.which2948 if peek=='ID' or peekToken.isKeyword2949 memberToken = .idOrKeyword2950 expr = MemberExpr(memberToken) to Expr2951 else2952 .throwError('Syntax error after "." Expecting variable Id ')2953 finally2954 .opStack.pop2955 return BinaryOpExpr.make(token to !, 'DOT', ThisLit(token), expr)2956 else if peek=='ID'2957 return .identifierExpr2958 else if .opStack.count and .opStack.peek=='DOT' and .peek.isKeyword2959 return .identifierExpr2960 2961 msg = 'Expecting an Id expression.'2962 if peekToken.isKeyword2963 msg += ' "[peekToken.text]" is a reserved keyword that is not expected here.'2964 .throwError(msg)2965 assert false2966 return ThisLit(token) # To shut c# compiler up re paths without return values - notreached2967 2968 2836 def forExpr as ForExpr 2969 2837 """ 2970 2838 t = for x in stuff where x<0 get x*x … … 3438 3306 token = .peek(peekAhead) 3439 3307 if token.which == 'ID' 3440 3308 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']) 3444 3310 3445 3311 def looksLikeVarNameIsNext(peekAhead as int) as bool 3446 3312 token = .peek(peekAhead) 3447 3313 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 3472 3319 def recordError(msg as String) as ParserException 3473 3320 return .recordError(.last, msg) 3474 3321 -
Tests/820-errors/300-statements/500-multi-assign.cobra
1 # compile failure cases for multiTarget assignment 2 class 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
9 9 10 10 pro p0 from _i0 11 11 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 13 22 def main is shared 14 23 ma = MultiAssign() 15 24 ma.nonLocalVar 16 25 ma.forIEnum 26 ma.forIndexor 17 27 ma.forSwap 18 28 19 29 def nonLocalVar … … 47 57 assert a == 1 48 58 assert b == 2 49 59 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 50 75 def forSwap 51 76 # common variable swap idiom ( python a,b = b,a) 52 77 a = 99 … … 54 79 a, b = [b, a] 55 80 assert a == 100 56 81 assert b == 99 82 -
Tests/320-misc-two/810-multi-assignment/120-multi-assign-fail.cobra
1 # test failure cases for multiTarget assignment 2 class 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
4 4 5 5 def main is shared 6 6 ls = [1, 2, 3, 4, 5, 6] 7 # ls needs to be pairs i.e [[1,2],[3,4],[5,6]] 7 8 for l1, l2 in ls # .error. Cannot find an indexer 8 9 assert l1 in [1, 3, 5] 9 10 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