coconut-develop 3.2.0.post0.dev13__tar.gz → 3.2.0.post0.dev15__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/DOCS.md +15 -13
  2. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/PKG-INFO +2 -23
  3. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/_pyparsing.py +2 -0
  4. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/compiler/compiler.py +119 -54
  5. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/compiler/grammar.py +14 -5
  6. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/compiler/header.py +1 -1
  7. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/compiler/matching.py +2 -0
  8. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/constants.py +2 -0
  9. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/root.py +1 -1
  10. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/tests/src/cocotest/agnostic/primary_1.coco +5 -5
  11. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/tests/src/cocotest/agnostic/primary_2.coco +16 -0
  12. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/tests/src/extras.coco +39 -9
  13. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/.claude/CLAUDE.md +0 -0
  14. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/.claude/settings.local.json +0 -0
  15. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/CONTRIBUTING.md +0 -0
  16. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/FAQ.md +0 -0
  17. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/HELP.md +0 -0
  18. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/LICENSE.txt +0 -0
  19. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/MANIFEST.in +0 -0
  20. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/README.rst +0 -0
  21. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/__coconut__/__init__.py +0 -0
  22. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/__coconut__/__init__.pyi +0 -0
  23. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/__coconut__/py.typed +0 -0
  24. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/_coconut/__init__.py +0 -0
  25. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/_coconut/__init__.pyi +0 -0
  26. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/_coconut/py.typed +0 -0
  27. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/__coconut__.py +0 -0
  28. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/__coconut__.pyi +0 -0
  29. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/__init__.py +0 -0
  30. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/__init__.pyi +0 -0
  31. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/__main__.py +0 -0
  32. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/api.py +0 -0
  33. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/api.pyi +0 -0
  34. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/command/__init__.py +0 -0
  35. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/command/__init__.pyi +0 -0
  36. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/command/cli.py +0 -0
  37. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/command/command.py +0 -0
  38. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/command/command.pyi +0 -0
  39. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/command/mypy.py +0 -0
  40. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/command/resources/zcoconut.pth +0 -0
  41. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/command/util.py +0 -0
  42. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/command/watch.py +0 -0
  43. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/compiler/__init__.py +0 -0
  44. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/compiler/templates/header.py_template +0 -0
  45. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/compiler/util.py +0 -0
  46. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/convenience.py +0 -0
  47. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/convenience.pyi +0 -0
  48. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/exceptions.py +0 -0
  49. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/highlighter.py +0 -0
  50. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/icoconut/__init__.py +0 -0
  51. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/icoconut/__main__.py +0 -0
  52. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/icoconut/coconut/kernel.json +0 -0
  53. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/icoconut/coconut_py/kernel.json +0 -0
  54. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/icoconut/coconut_py2/kernel.json +0 -0
  55. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/icoconut/coconut_py3/kernel.json +0 -0
  56. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/icoconut/embed.py +0 -0
  57. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/icoconut/root.py +0 -0
  58. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/integrations.py +0 -0
  59. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/main.py +0 -0
  60. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/py.typed +0 -0
  61. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/requirements.py +0 -0
  62. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/terminal.py +0 -0
  63. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/tests/__init__.py +0 -0
  64. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/tests/__main__.py +0 -0
  65. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/tests/constants_test.py +0 -0
  66. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/tests/main_test.py +0 -0
  67. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/tests/src/cocotest/agnostic/__init__.coco +0 -0
  68. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/tests/src/cocotest/agnostic/__main__.coco +0 -0
  69. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/tests/src/cocotest/agnostic/main.coco +0 -0
  70. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/tests/src/cocotest/agnostic/specific.coco +0 -0
  71. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/tests/src/cocotest/agnostic/suite.coco +0 -0
  72. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/tests/src/cocotest/agnostic/tutorial.coco +0 -0
  73. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/tests/src/cocotest/agnostic/util.coco +0 -0
  74. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/tests/src/cocotest/non_strict/non_strict_test.coco +0 -0
  75. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/tests/src/cocotest/target_2/py2_test.coco +0 -0
  76. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/tests/src/cocotest/target_3/py3_test.coco +0 -0
  77. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/tests/src/cocotest/target_311/py311_test.coco +0 -0
  78. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/tests/src/cocotest/target_314/py314_test.coco +0 -0
  79. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/tests/src/cocotest/target_35/py35_test.coco +0 -0
  80. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/tests/src/cocotest/target_36/py36_test.coco +0 -0
  81. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/tests/src/cocotest/target_38/py38_test.coco +0 -0
  82. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/tests/src/cocotest/target_sys/target_sys_test.coco +0 -0
  83. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/tests/src/importable.coco +0 -0
  84. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/tests/src/runnable.coco +0 -0
  85. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/tests/src/runner.coco +0 -0
  86. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut/util.py +0 -0
  87. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/coconut_develop.egg-info/SOURCES.txt +0 -0
  88. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/conf.py +0 -0
  89. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/pyproject.toml +0 -0
  90. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/setup.cfg +0 -0
  91. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/setup.py +0 -0
  92. {coconut_develop-3.2.0.post0.dev13 → coconut_develop-3.2.0.post0.dev15}/xontrib/coconut.py +0 -0
@@ -1197,6 +1197,8 @@ infix_pattern ::= bar_or_pattern ("`" EXPR "`" [EXPR])* # infix check
1197
1197
 
1198
1198
  bar_or_pattern ::= pattern ("|" pattern)* # match any
1199
1199
 
1200
+ capture ::= NAME | "(" pattern ")" # in unambiguous capture positions
1201
+
1200
1202
  base_pattern ::= (
1201
1203
  "(" pattern ")" # parentheses
1202
1204
  | "None" | "True" | "False" # constants
@@ -1230,29 +1232,29 @@ base_pattern ::= (
1230
1232
  | [( # sequence splits
1231
1233
  "(" patterns ")"
1232
1234
  | "[" patterns "]"
1233
- ) "+"] NAME ["+" (
1235
+ ) "+"] capture ["+" (
1234
1236
  "(" patterns ")" # this match must be the same
1235
1237
  | "[" patterns "]" # construct as the first match
1236
- )] ["+" NAME ["+" (
1237
- "(" patterns ")" # and same here
1238
+ )] ["+" NAME ["+" ( # search splits require NAME
1239
+ "(" patterns ")" # must be same as first match
1238
1240
  | "[" patterns "]"
1239
1241
  )]]
1240
1242
  | [( # iterable splits
1241
1243
  "(" patterns ")"
1242
1244
  | "[" patterns "]"
1243
1245
  | "(|" patterns "|)"
1244
- ) "::"] NAME ["::" (
1246
+ ) "::"] capture ["::" (
1245
1247
  "(" patterns ")"
1246
1248
  | "[" patterns "]"
1247
1249
  | "(|" patterns "|)"
1248
- )] [ "::" NAME [
1250
+ )] [ "::" NAME [ # search splits require NAME
1249
1251
  "(" patterns ")"
1250
1252
  | "[" patterns "]"
1251
1253
  | "(|" patterns "|)"
1252
1254
  ]]
1253
- | [STRING "+"] NAME # complex string matching
1255
+ | [STRING "+"] capture # complex string matching
1254
1256
  ["+" STRING]
1255
- ["+" NAME ["+" STRING]]
1257
+ ["+" NAME ["+" STRING]] # search splits require NAME
1256
1258
  )
1257
1259
  ```
1258
1260
 
@@ -1287,13 +1289,13 @@ base_pattern ::= (
1287
1289
  - Sequence Destructuring:
1288
1290
  - Lists (`[<patterns>]`), Tuples (`(<patterns>)`): will only match a sequence (`collections.abc.Sequence`) of the same length, and will check the contents against `<patterns>` (Coconut automatically registers `numpy` arrays and `collections.deque` objects as sequences).
1289
1291
  - Lazy lists (`(|<patterns>|)`): same as list or tuple matching, but checks for an Iterable (`collections.abc.Iterable`) instead of a Sequence.
1290
- - Head-Tail Splits (`<list/tuple> + <var>` or `(<patterns>, *<var>)`): will match the beginning of the sequence against the `<list/tuple>`/`<patterns>`, then bind the rest to `<var>`, and make it the type of the construct used.
1291
- - Init-Last Splits (`<var> + <list/tuple>` or `(*<var>, <patterns>)`): exactly the same as head-tail splits, but on the end instead of the beginning of the sequence.
1292
- - Head-Last Splits (`<list/tuple> + <var> + <list/tuple>` or `(<patterns>, *<var>, <patterns>)`): the combination of a head-tail and an init-last split.
1293
- - Search Splits (`<var1> + <list/tuple> + <var2>` or `(*<var1>, <patterns>, *<var2>)`): searches for the first occurrence of the `<list/tuple>`/`<patterns>` in the sequence, then puts everything before into `<var1>` and everything after into `<var2>`.
1292
+ - Head-Tail Splits (`<list/tuple> + <capture>` or `(<patterns>, *<var>)`): will match the beginning of the sequence against the `<list/tuple>`/`<patterns>`, then bind the rest to `<capture>`, and make it the type of the construct used. `<capture>` can be a variable name or a parenthesized match pattern (e.g. `(int -> x)`).
1293
+ - Init-Last Splits (`<capture> + <list/tuple>` or `(*<var>, <patterns>)`): exactly the same as head-tail splits, but on the end instead of the beginning of the sequence.
1294
+ - Head-Last Splits (`<list/tuple> + <capture> + <list/tuple>` or `(<patterns>, *<var>, <patterns>)`): the combination of a head-tail and an init-last split.
1295
+ - Search Splits (`<var1> + <list/tuple> + <var2>` or `(*<var1>, <patterns>, *<var2>)`): searches for the first occurrence of the `<list/tuple>`/`<patterns>` in the sequence, then puts everything before into `<var1>` and everything after into `<var2>`. Search split captures must be variable names, not parenthesized patterns.
1294
1296
  - Head-Last Search Splits (`<list/tuple> + <var> + <list/tuple> + <var> + <list/tuple>` or `(<patterns>, *<var>, <patterns>, *<var>, <patterns>)`): the combination of a head-tail split and a search split.
1295
- - Iterable Splits (`<list/tuple/lazy list> :: <var> :: <list/tuple/lazy list> :: <var> :: <list/tuple/lazy list>`): same as other sequence destructuring, but works on any iterable (`collections.abc.Iterable`), including infinite iterators (note that if an iterator is matched against it will be modified unless it is [`reiterable`](#reiterable)).
1296
- - Complex String Matching (`<string> + <var> + <string> + <var> + <string>`): string matching supports the same destructuring options as above.
1297
+ - Iterable Splits (`<list/tuple/lazy list> :: <capture> :: <list/tuple/lazy list> :: <var> :: <list/tuple/lazy list>`): same as other sequence destructuring, but works on any iterable (`collections.abc.Iterable`), including infinite iterators (note that if an iterator is matched against it will be modified unless it is [`reiterable`](#reiterable)).
1298
+ - Complex String Matching (`<string> + <capture> + <string> + <var> + <string>`): string matching supports the same destructuring options as above. In unambiguous positions (single capture), `<capture>` can be a variable name or a parenthesized match pattern (e.g. `(int -> n) + "px"`).
1297
1299
 
1298
1300
  _Note: Like [iterator slicing](#iterator-slicing), iterator and lazy list matching make no guarantee that the original iterator matched against be preserved (to preserve the iterator, use Coconut's [`reiterable`](#reiterable) built-in)._
1299
1301
 
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: coconut-develop
3
- Version: 3.2.0.post0.dev13
3
+ Version: 3.2.0.post0.dev15
4
4
  Summary: Simple, elegant, Pythonic functional programming.
5
5
  Home-page: http://coconut-lang.org
6
6
  Author: Evan Hubinger
7
7
  Author-email: evanjhub@gmail.com
8
8
  License: Apache-2.0
9
- Keywords: functional,programming,language,compiler,pattern,pattern-matching,algebraic,data type,data types,lambda,lambdas,evaluation,lazy list,lazy lists,tail,recursion,call,recursive,recursive_iterator,infix,function,composition,compose,partial,application,currying,curry,pipeline,pipe,unicode,operator,operators,frozenset,literal,syntax,destructuring,assignment,fold,datamaker,prepattern,iterator,generator,none,coalesce,coalescing,statement,lru_cache,memoization,backport,typing,embed,PEP 622,overrides,islice,itertools,functools,TYPE_CHECKING,Expected,breakpoint,help,reduce,takewhile,dropwhile,tee,count,makedata,consume,process_map,thread_map,addpattern,recursive_generator,fmap,starmap,reiterable,scan,groupsof,memoize,zip_longest,override,flatten,ident,call,safe_call,flip,const,lift,lift_apart,all_equal,collectby,mapreduce,multi_enumerate,cartesian_product,multiset,cycle,windowsof,and_then,and_then_await,async_map,py_chr,py_dict,py_hex,py_input,py_int,py_map,py_object,py_oct,py_open,py_print,py_range,py_str,py_super,py_zip,py_filter,py_reversed,py_enumerate,py_raw_input,py_xrange,py_repr,py_breakpoint,py_min,py_max,_namedtuple_of,reveal_type,reveal_locals,MatchError,CoconutWarning,__fmap__,__iter_getitem__,data,match,case,cases,where,final,addpattern,then,operator,type,copyclosure,lazy,λ
9
+ Keywords: functional,programming,language,compiler,pattern,pattern-matching,algebraic,data type,data types,lambda,lambdas,evaluation,lazy list,lazy lists,tail,recursion,call,recursive,recursive_iterator,infix,function,composition,compose,partial,application,currying,curry,pipeline,pipe,unicode,operator,operators,frozenset,literal,syntax,destructuring,assignment,fold,datamaker,prepattern,iterator,generator,none,coalesce,coalescing,statement,lru_cache,memoization,backport,typing,embed,PEP 622,overrides,islice,itertools,functools,TYPE_CHECKING,Expected,breakpoint,help,reduce,takewhile,dropwhile,tee,count,makedata,consume,process_map,thread_map,addpattern,recursive_generator,fmap,starmap,reiterable,scan,groupsof,memoize,zip_longest,override,flatten,ident,call,safe_call,flip,const,lift,lift_apart,all_equal,collectby,mapreduce,multi_enumerate,cartesian_product,multiset,cycle,windowsof,and_then,and_then_await,async_map,py_bytes,py_chr,py_dict,py_hex,py_input,py_int,py_map,py_object,py_oct,py_open,py_print,py_range,py_str,py_super,py_zip,py_filter,py_reversed,py_enumerate,py_raw_input,py_xrange,py_repr,py_breakpoint,py_min,py_max,_namedtuple_of,reveal_type,reveal_locals,MatchError,CoconutWarning,__fmap__,__iter_getitem__,data,match,case,cases,where,final,addpattern,then,operator,type,copyclosure,lazy,λ
10
10
  Classifier: Development Status :: 5 - Production/Stable
11
11
  Classifier: Intended Audience :: Developers
12
12
  Classifier: Topic :: Software Development
@@ -220,27 +220,6 @@ Requires-Dist: pytest>=7; (python_version >= "3.6" and python_version < "3.8") a
220
220
  Requires-Dist: pytest<8.1,>=8.0; python_version >= "3.8" and extra == "tests"
221
221
  Requires-Dist: pexpect>=4; extra == "tests"
222
222
  Requires-Dist: pytest_remotedata>=0.3; extra == "tests"
223
- Requires-Dist: ipython>=5.4; python_version < "3" and extra == "tests"
224
- Requires-Dist: ipython<7.10,>=7.9; (python_version >= "3" and python_version < "3.7") and extra == "tests"
225
- Requires-Dist: ipython>=7.34; (python_version >= "3.7" and python_version < "3.8") and extra == "tests"
226
- Requires-Dist: ipython>=8.12; (python_version >= "3.8" and python_version < "3.9") and extra == "tests"
227
- Requires-Dist: ipython>=8.18; (python_version >= "3.9" and python_version < "3.11") and extra == "tests"
228
- Requires-Dist: ipython>=9; python_version >= "3.11" and extra == "tests"
229
- Requires-Dist: ipykernel>=4.10; python_version < "3" and extra == "tests"
230
- Requires-Dist: ipykernel>=5.5; (python_version >= "3" and python_version < "3.8") and extra == "tests"
231
- Requires-Dist: ipykernel>=6; (python_version >= "3.8" and python_version < "3.10") and extra == "tests"
232
- Requires-Dist: ipykernel>=7; python_version >= "3.10" and extra == "tests"
233
- Requires-Dist: jupyter-client>=5.3; python_version < "3.5" and extra == "tests"
234
- Requires-Dist: jupyter-client<6.1.13,>=6.1.12; (python_version >= "3.5" and python_version < "3.6") and extra == "tests"
235
- Requires-Dist: jupyter-client>=7.1.2; python_version >= "3.6" and extra == "tests"
236
- Requires-Dist: jedi<0.18,>=0.17; python_version < "3.9" and extra == "tests"
237
- Requires-Dist: jedi>=0.19; python_version >= "3.9" and extra == "tests"
238
- Requires-Dist: pywinpty<0.6,>=0.5; (python_version < "3" and os_name == "nt") and extra == "tests"
239
- Requires-Dist: jupyter>=1.1; extra == "tests"
240
- Requires-Dist: jupyter-console>=5.2; python_version < "3.5" and extra == "tests"
241
- Requires-Dist: jupyter-console>=6.1; (python_version >= "3.5" and python_version < "3.7") and extra == "tests"
242
- Requires-Dist: jupyter-console>=6.6; python_version >= "3.7" and extra == "tests"
243
- Requires-Dist: papermill>=1.2; extra == "tests"
244
223
  Provides-Extra: dev
245
224
  Requires-Dist: ipython>=5.4; python_version < "3" and extra == "dev"
246
225
  Requires-Dist: ipython<7.10,>=7.9; (python_version >= "3" and python_version < "3.7") and extra == "dev"
@@ -45,6 +45,7 @@ from coconut.constants import (
45
45
  default_incremental_cache_size,
46
46
  never_clear_incremental_cache,
47
47
  warn_on_multiline_regex,
48
+ warn_on_undefined_ParseResults_name,
48
49
  num_displayed_timing_items,
49
50
  use_pyparsing_cache_file,
50
51
  use_line_by_line_parser,
@@ -274,6 +275,7 @@ if DEVELOP:
274
275
  _pyparsing._enable_all_warnings()
275
276
  _pyparsing.__diag__.warn_name_set_on_empty_Forward = False
276
277
  _pyparsing.__diag__.warn_on_incremental_multiline_regex = warn_on_multiline_regex
278
+ _pyparsing.__diag__.warn_on_undefined_ParseResults_name = warn_on_undefined_ParseResults_name
277
279
 
278
280
  if MODERN_PYPARSING and use_left_recursion_if_available:
279
281
  ParserElement.enable_left_recursion()
@@ -632,18 +632,21 @@ class Compiler(Grammar, pickleable_obj):
632
632
  temp_var_counts = None
633
633
  operators = None
634
634
 
635
- def get_empty_scope(self):
635
+ def get_empty_scope(self, inner=False):
636
636
  """Get an empty scope for the parsing_context."""
637
+ parent = self.current_parsing_context("scope")
637
638
  return {
638
639
  "final_vars": {},
639
640
  "pure_vars": {},
641
+ "all_vars": None if inner else set(),
642
+ "parent": parent,
640
643
  }
641
644
 
642
- def init_parsing_context(self):
645
+ def init_parsing_context(self, inner=False):
643
646
  """Initialize parsing context."""
644
647
  self.parsing_context = defaultdict(list)
645
648
  # initialize module-level scopes
646
- self.parsing_context["scope"].append(self.get_empty_scope())
649
+ self.parsing_context["scope"].append(self.get_empty_scope(inner=inner))
647
650
  return self.parsing_context
648
651
 
649
652
  def reset(self, keep_state=False, filename=None):
@@ -680,6 +683,7 @@ class Compiler(Grammar, pickleable_obj):
680
683
  self.shown_warnings = set()
681
684
  if not keep_state:
682
685
  self.computation_graph_caches = defaultdict(staledict)
686
+ self.final_checks = []
683
687
  self.init_parsing_context()
684
688
 
685
689
  @contextmanager
@@ -699,7 +703,7 @@ class Compiler(Grammar, pickleable_obj):
699
703
  remaining_original, self.remaining_original = self.remaining_original, None
700
704
  shown_warnings, self.shown_warnings = self.shown_warnings, set()
701
705
  parsing_context = self.parsing_context
702
- self.init_parsing_context()
706
+ self.init_parsing_context(inner=True)
703
707
  try:
704
708
  with ComputationNode.using_overrides():
705
709
  yield
@@ -890,20 +894,32 @@ class Compiler(Grammar, pickleable_obj):
890
894
 
891
895
  # name handlers
892
896
  cls.refname <<= attach(cls.name_ref, cls.method("name_handle"))
893
- cls.nonfinal_setname <<= attach(cls.name_ref, cls.method("name_handle", assign=True))
897
+ cls.nonfinal_setname <<= attach(
898
+ cls.name_ref,
899
+ cls.method("name_handle", assign=True),
900
+ )
894
901
  cls.final_setname <<= attach(
895
902
  cls.final_setname_ref,
896
903
  cls.method("name_handle", assign=True, is_final=True),
897
904
  greedy=True,
898
905
  )
906
+ cls.nonfinal_outer_setname <<= attach(
907
+ cls.name_ref,
908
+ cls.method("name_handle", assign=True, outer_setname=True),
909
+ )
910
+ cls.final_outer_setname <<= attach(
911
+ cls.final_setname_ref,
912
+ cls.method("name_handle", assign=True, outer_setname=True, is_final=True),
913
+ greedy=True,
914
+ )
899
915
  cls.nonfinal_classname <<= attach(
900
916
  cls.name_ref,
901
- cls.method("name_handle", assign=True, classname=True),
917
+ cls.method("name_handle", assign=True, outer_setname=True, classname=True),
902
918
  greedy=True,
903
919
  )
904
920
  cls.final_classname <<= attach(
905
921
  cls.final_setname_ref,
906
- cls.method("name_handle", assign=True, classname=True, is_final=True),
922
+ cls.method("name_handle", assign=True, outer_setname=True, classname=True, is_final=True),
907
923
  greedy=True,
908
924
  )
909
925
  cls.expr_setname <<= attach(
@@ -1541,10 +1557,8 @@ class Compiler(Grammar, pickleable_obj):
1541
1557
  if info["imported"] and not info["referenced"]:
1542
1558
  for loc in info["imported"]:
1543
1559
  self.strict_err_or_warn("found unused import " + repr(self.reformat(name, ignore_errors=True)), original, loc, noqa_able=True, endpoint=False)
1544
- if not self.star_import: # only check for undefined names when there are no * imports
1545
- if name not in all_builtins and info["referenced"] and not (info["assigned"] or info["imported"]):
1546
- for loc in info["referenced"]:
1547
- self.strict_err_or_warn("found undefined name " + repr(self.reformat(name, ignore_errors=True)), original, loc, noqa_able=True, endpoint=False)
1560
+ for final_check in self.final_checks:
1561
+ final_check()
1548
1562
 
1549
1563
  def pickle_cache(self, original, cache_path, include_incremental=True):
1550
1564
  """Pickle the pyparsing cache for original to cache_path."""
@@ -4058,7 +4072,7 @@ else:
4058
4072
  type_ignore=self.type_ignore_comment(),
4059
4073
  )
4060
4074
 
4061
- def _make_import_stmt(self, imp_from, imp, imp_as, raw=False, lazy=False):
4075
+ def make_import_stmt(self, imp_from, imp, imp_as, raw=False, lazy=False):
4062
4076
  """Generate an import statement."""
4063
4077
  if not raw and imp != "*":
4064
4078
  module_path = (imp if imp_from is None else imp_from).split(".", 1)
@@ -4075,7 +4089,7 @@ else:
4075
4089
  raise _coconut.ImportError(_coconut.str(_coconut_imp_err))
4076
4090
  """,
4077
4091
  ).format(
4078
- raw_import=self._make_import_stmt(imp_from, imp, imp_as, raw=True),
4092
+ raw_import=self.make_import_stmt(imp_from, imp, imp_as, raw=True),
4079
4093
  imp_name=imp_as if imp_as is not None else imp,
4080
4094
  imp_lookup=".".join([existing_imp] + module_path[1:] + ([imp] if imp_from is not None else [])),
4081
4095
  )
@@ -4121,14 +4135,14 @@ else:
4121
4135
 
4122
4136
  if imp_as is not None and "." in imp_as:
4123
4137
  import_as_var = self.get_temp_var("import", loc)
4124
- out.append(self._make_import_stmt(imp_from, imp, import_as_var, lazy=lazy))
4138
+ out.append(self.make_import_stmt(imp_from, imp, import_as_var, lazy=lazy))
4125
4139
  fake_mods = imp_as.split(".")
4126
4140
  for i in range(1, len(fake_mods)):
4127
4141
  mod_name = ".".join(fake_mods[:i])
4128
4142
  out.append(self.ensure_module_or_create_fake(mod_name))
4129
4143
  out.append(".".join(fake_mods) + " = " + import_as_var)
4130
4144
  else:
4131
- out.append(self._make_import_stmt(imp_from, imp, imp_as, lazy=lazy))
4145
+ out.append(self.make_import_stmt(imp_from, imp, imp_as, lazy=lazy))
4132
4146
 
4133
4147
  if type_ignore:
4134
4148
  for i, line in enumerate(out):
@@ -4579,7 +4593,7 @@ def {name}({match_func_paramdef}):
4579
4593
 
4580
4594
  self.add_code_before[name] = self.decoratable_funcdef_stmt_handle(original, loc, [decorators, funcdef], is_async, is_stmt_lambda=True)
4581
4595
 
4582
- return self._handle_expr_scope_closure(name, loc)
4596
+ return self.handle_expr_scope_closure(name, loc)
4583
4597
 
4584
4598
  def match_comp_expr_handle(self, original, loc, tokens, dict_val=None):
4585
4599
  """Build a match comprehension by creating a temp match function.
@@ -4619,26 +4633,31 @@ def {func_name}({iter_var}):
4619
4633
 
4620
4634
  self.add_code_before[func_name] = self.decoratable_funcdef_stmt_handle(original, loc, [funcdef], is_stmt_lambda=True)
4621
4635
 
4622
- func_expr = self._handle_expr_scope_closure(func_name, loc)
4636
+ func_expr = self.handle_expr_scope_closure(func_name, loc)
4623
4637
 
4624
4638
  if dict_val is not None:
4625
4639
  return "_coconut.dict((" + func_expr + "(" + iter_var + ") for " + iter_var + " in " + iterable + "))"
4626
4640
  else:
4627
4641
  return func_expr + "(" + iter_var + ") for " + iter_var + " in " + iterable
4628
4642
 
4629
- def _handle_expr_scope_closure(self, name, loc):
4643
+ def get_parent_expr_setnames(self):
4644
+ """Get all expr_setnames in parent contexts, but not the current context."""
4645
+ expr_setname_context = self.current_parsing_context("expr_setnames")
4646
+ parent_context = expr_setname_context["parent"]
4647
+ parent_setnames = set()
4648
+ while parent_context:
4649
+ parent_setnames |= parent_context["new_names"]
4650
+ parent_context = parent_context["parent"]
4651
+ return parent_setnames
4652
+
4653
+ def handle_expr_scope_closure(self, name, loc):
4630
4654
  """Extracts the definition of a name to a separate function that closes on all local expr setnames."""
4631
4655
  expr_setname_context = self.current_parsing_context("expr_setnames")
4632
4656
  if expr_setname_context is None:
4633
4657
  return name
4634
4658
  else:
4635
4659
  builder_name = self.get_temp_var("lambda_builder", loc)
4636
-
4637
- parent_context = expr_setname_context["parent"]
4638
- parent_setnames = set()
4639
- while parent_context:
4640
- parent_setnames |= parent_context["new_names"]
4641
- parent_context = parent_context["parent"]
4660
+ parent_setnames = self.get_parent_expr_setnames()
4642
4661
 
4643
4662
  def stmt_lambdef_callback():
4644
4663
  expr_setnames = parent_setnames | expr_setname_context["new_names"]
@@ -5569,12 +5588,14 @@ class {protocol_var}({tokens}, _coconut.typing.Protocol): pass
5569
5588
  ):
5570
5589
  yield
5571
5590
 
5572
- def name_handle(self, original, loc, tokens, assign=False, classname=False, expr_setname=False, is_final=False):
5591
+ def name_handle(self, original, loc, tokens, assign=False, outer_setname=False, expr_setname=False, classname=False, is_final=False):
5573
5592
  """Handle the given base name."""
5574
- if expr_setname:
5575
- internal_assert(assign, "expr_setname should always imply assign", (expr_setname, assign))
5593
+ if classname:
5594
+ internal_assert(outer_setname and not expr_setname, "classname should always imply outer_setname", tokens)
5595
+ if outer_setname or expr_setname:
5596
+ internal_assert(assign, "classname/funcname/expr_setname should always imply assign", tokens)
5576
5597
  if is_final:
5577
- internal_assert(assign and not expr_setname, "only setnames should ever be final", (assign, is_final))
5598
+ internal_assert(assign and not expr_setname, "only setnames should ever be final", tokens)
5578
5599
 
5579
5600
  name, = tokens
5580
5601
 
@@ -5595,24 +5616,6 @@ class {protocol_var}({tokens}, _coconut.typing.Protocol): pass
5595
5616
  always_wrap=is_greedy,
5596
5617
  )
5597
5618
 
5598
- # register non-mid-expression variable assignments inside of where statements for later mangling
5599
- if assign and not expr_setname:
5600
- where_context = self.current_parsing_context("where")
5601
- if where_context is not None:
5602
- where_assigns = where_context["assigns"]
5603
- if where_assigns is not None:
5604
- where_assigns.add(name)
5605
-
5606
- if classname:
5607
- cls_context = self.current_parsing_context("class")
5608
- self.internal_assert(cls_context is not None, original, loc, "found classname outside of class", tokens)
5609
- cls_context["name"] = name
5610
-
5611
- if expr_setname:
5612
- expr_setnames_context = self.current_parsing_context("expr_setnames")
5613
- self.internal_assert(expr_setnames_context is not None, original, loc, "found expr_setname outside of has_expr_setname_manage", tokens)
5614
- expr_setnames_context["new_names"].add(name)
5615
-
5616
5619
  if not escaped:
5617
5620
  typevar_info = self.current_parsing_context("typevars")
5618
5621
  if typevar_info is not None:
@@ -5633,18 +5636,45 @@ class {protocol_var}({tokens}, _coconut.typing.Protocol): pass
5633
5636
  # note that this is the one case where we return early that isn't an error
5634
5637
  return typevars[name]
5635
5638
 
5639
+ # register non-mid-expression variable assignments inside of where statements for later mangling
5640
+ if assign and not expr_setname:
5641
+ where_context = self.current_parsing_context("where")
5642
+ if where_context is not None:
5643
+ where_assigns = where_context["assigns"]
5644
+ if where_assigns is not None:
5645
+ where_assigns.add(name)
5646
+
5647
+ cls_context = self.current_parsing_context("class")
5648
+ expr_setnames_context = self.current_parsing_context("expr_setnames")
5649
+
5650
+ if classname:
5651
+ self.internal_assert(cls_context is not None, original, loc, "found classname outside of class", tokens)
5652
+ cls_context["name"] = name
5653
+
5654
+ if expr_setname:
5655
+ self.internal_assert(expr_setnames_context is not None, original, loc, "found expr_setname outside of has_expr_setname_manage", tokens)
5656
+ expr_setnames_context["new_names"].add(name)
5657
+
5658
+ scope = self.current_parsing_context("scope")
5659
+ self.internal_assert(scope is not None, original, loc, "no scope context")
5660
+
5636
5661
  # track assigned/referenced early so that even if we return early with an
5637
5662
  # error, the name is marked as used (e.g. for unused import checking)
5638
5663
  if assign:
5639
5664
  is_new = loc not in self.name_info[name]["assigned"]
5640
5665
  self.name_info[name]["assigned"].add(loc)
5666
+ if (
5667
+ outer_setname
5668
+ and scope["parent"] is not None
5669
+ and scope["parent"]["all_vars"] is not None
5670
+ ):
5671
+ scope["parent"]["all_vars"].add(name)
5672
+ elif not expr_setname and scope["all_vars"] is not None:
5673
+ scope["all_vars"].add(name)
5641
5674
  else:
5642
5675
  is_new = loc not in self.name_info[name]["referenced"]
5643
5676
  self.name_info[name]["referenced"].add(loc)
5644
5677
 
5645
- scope = self.current_parsing_context("scope")
5646
- self.internal_assert(scope is not None, original, loc, "no scope context")
5647
-
5648
5678
  # final variable checking (setting final_vars happens at the end)
5649
5679
  final_vars = scope["final_vars"]
5650
5680
  if (
@@ -5693,7 +5723,8 @@ class {protocol_var}({tokens}, _coconut.typing.Protocol): pass
5693
5723
  return err
5694
5724
 
5695
5725
  is_class_attr = (
5696
- self.current_parsing_context("class")
5726
+ assign
5727
+ and cls_context
5697
5728
  and not self.in_method
5698
5729
  and (
5699
5730
  # for classnames, we need special handling for nested classes
@@ -5712,14 +5743,14 @@ class {protocol_var}({tokens}, _coconut.typing.Protocol): pass
5712
5743
  if (
5713
5744
  assign
5714
5745
  and not escaped
5715
- # if we're creating a class attribute, then we're not actually shadowing anything
5716
- and not is_class_attr
5717
5746
  # if we're not using the computation graph, then name is handled
5718
5747
  # greedily, which means this might be an invalid parse, in which
5719
5748
  # case we can't be sure this is actually shadowing a builtin;
5720
5749
  # BUT if we're on strict mode, then it's an actual error, rather
5721
5750
  # than a warning, which means we can just wrap it
5722
5751
  and safe_to_show_warnings
5752
+ # if we're creating a class attribute, then we're not actually shadowing anything
5753
+ and not is_class_attr
5723
5754
  ):
5724
5755
  if name in all_builtins:
5725
5756
  err = self.strict_err_or_warn(
@@ -5735,7 +5766,7 @@ class {protocol_var}({tokens}, _coconut.typing.Protocol): pass
5735
5766
  # the import statement itself, not an assignment shadowing an import
5736
5767
  if any(not same_line(original, loc, imp_loc) for imp_loc in self.name_info[name]["imported"]):
5737
5768
  err = self.strict_err_or_warn(
5738
- "assignment shadows imported name '{name}' (use explicit '\\{name}' syntax when purposefully redefining imported names)".format(name=name),
5769
+ "assignment shadows imported '{name}' (use explicit '\\{name}' syntax when purposefully redefining imported names)".format(name=name),
5739
5770
  original,
5740
5771
  loc,
5741
5772
  raise_err_func=local_raise_or_wrap_error,
@@ -5743,6 +5774,22 @@ class {protocol_var}({tokens}, _coconut.typing.Protocol): pass
5743
5774
  if err is not None:
5744
5775
  return err
5745
5776
 
5777
+ # undefined name checking
5778
+ if (
5779
+ not assign
5780
+ and not escaped
5781
+ and safe_to_show_warnings
5782
+ and not self.star_import
5783
+ and scope["all_vars"] is not None
5784
+ and name not in all_builtins
5785
+ and name != wildcard
5786
+ and (expr_setnames_context is None or (
5787
+ name not in expr_setnames_context["new_names"]
5788
+ and name not in self.get_parent_expr_setnames()
5789
+ ))
5790
+ ):
5791
+ self.final_checks.append(partial(self.check_undefined_name, original, loc, name, scope, self.outer_ln))
5792
+
5746
5793
  # only mark as final after all checks pass
5747
5794
  if is_final:
5748
5795
  final_vars[name] = loc
@@ -5772,7 +5819,6 @@ class {protocol_var}({tokens}, _coconut.typing.Protocol): pass
5772
5819
  return "_coconut_exec"
5773
5820
  elif not assign and name in super_names and not self.target.startswith("3"):
5774
5821
  if self.in_method:
5775
- cls_context = self.current_parsing_context("class")
5776
5822
  enclosing_cls = cls_context["name_prefix"] + cls_context["name"]
5777
5823
  return self.add_code_before_marker_with_replacement(name, "__class__ = " + enclosing_cls + "\n", add_spaces=False)
5778
5824
  else:
@@ -5787,6 +5833,25 @@ class {protocol_var}({tokens}, _coconut.typing.Protocol): pass
5787
5833
  else:
5788
5834
  return name
5789
5835
 
5836
+ def check_undefined_name(self, original, loc, name, scope, outer_ln):
5837
+ """Run a deferred check for name being undefined in the scope chain.
5838
+
5839
+ We need outer_ln=self.outer_ln to be put in the partial so we know what
5840
+ the outer_ln was when this check was set up."""
5841
+ current_scope = scope
5842
+ while current_scope is not None:
5843
+ if current_scope["all_vars"] is None or name in current_scope["all_vars"]:
5844
+ return
5845
+ current_scope = current_scope["parent"]
5846
+ self.strict_err_or_warn(
5847
+ "found undefined name '{name}' (use explicit '\\{name}' syntax to bypass)".format(name=name),
5848
+ original,
5849
+ loc,
5850
+ ln=outer_ln,
5851
+ noqa_able=True,
5852
+ endpoint=False,
5853
+ )
5854
+
5790
5855
  # end: MANAGERS
5791
5856
  # -----------------------------------------------------------------------------------------------------------------------
5792
5857
  # CHECKING HANDLERS:
@@ -861,18 +861,23 @@ class Grammar(object):
861
861
  expr_setname = Forward()
862
862
  final_setname = Forward()
863
863
  nonfinal_setname = Forward()
864
+ # classname and outer_setname define their vars in the outer scope
864
865
  final_classname = Forward()
865
866
  nonfinal_classname = Forward()
867
+ final_outer_setname = Forward()
868
+ nonfinal_outer_setname = Forward()
866
869
 
867
870
  name_ref = combine(Optional(backslash) + base_name)
868
871
  final_setname_ref = keyword("final").suppress() + name_ref
869
872
  setname = final_setname | nonfinal_setname
870
873
  classname = final_classname | nonfinal_classname
874
+ outer_setname = final_outer_setname | nonfinal_outer_setname
871
875
  unsafe_name = combine(Optional(backslash.suppress()) + base_name)
872
876
 
873
877
  # use unsafe_name for dotted components since name should only be used for base names
874
878
  dotted_refname = condense(refname + ZeroOrMore(dot + unsafe_name))
875
879
  dotted_setname = condense(setname + ZeroOrMore(dot + unsafe_name))
880
+ dotted_outer_setname = condense(outer_setname + ZeroOrMore(dot + unsafe_name))
876
881
  unsafe_dotted_name = condense(unsafe_name + ZeroOrMore(dot + unsafe_name))
877
882
  must_be_dotted_name = condense(refname + OneOrMore(dot + unsafe_name))
878
883
 
@@ -1566,7 +1571,7 @@ class Grammar(object):
1566
1571
  type_params = Group(lbrack.suppress() + tokenlist(type_param, comma) + rbrack.suppress())
1567
1572
 
1568
1573
  type_alias_stmt = Forward()
1569
- type_alias_stmt_ref = keyword("type").suppress() + setname + Optional(type_params) + equals.suppress() + typedef_test
1574
+ type_alias_stmt_ref = keyword("type").suppress() + outer_setname + Optional(type_params) + equals.suppress() + typedef_test
1570
1575
 
1571
1576
  await_expr = Forward()
1572
1577
  await_expr_ref = keyword("await").suppress() + atom_item
@@ -2056,6 +2061,10 @@ class Grammar(object):
2056
2061
  del_stmt = addspace(keyword("del") - simple_assignlist)
2057
2062
 
2058
2063
  interior_name_match = labeled_group(setname, "var")
2064
+ interior_capture_match = (
2065
+ interior_name_match
2066
+ | labeled_group(lparen.suppress() + match + rparen.suppress(), "paren")
2067
+ )
2059
2068
  matchlist_anon_named_tuple_item = (
2060
2069
  Group(Optional(dot) + unsafe_name) + equals + match
2061
2070
  | Group(Optional(dot) + interior_name_match) + equals
@@ -2103,18 +2112,18 @@ class Grammar(object):
2103
2112
  match_string = interleaved_tokenlist(
2104
2113
  # f_string_atom must come first
2105
2114
  f_string_atom("f_string") | fixed_len_string_tokens("string"),
2106
- interior_name_match("capture"),
2115
+ interior_capture_match("capture"),
2107
2116
  plus,
2108
2117
  at_least_two=True,
2109
2118
  )("string_sequence")
2110
2119
  sequence_match = interleaved_tokenlist(
2111
2120
  (match_list | match_tuple)("literal"),
2112
- interior_name_match("capture"),
2121
+ interior_capture_match("capture"),
2113
2122
  plus,
2114
2123
  )("sequence")
2115
2124
  iter_match = interleaved_tokenlist(
2116
2125
  (match_list | match_tuple | match_lazy)("literal"),
2117
- interior_name_match("capture"),
2126
+ interior_capture_match("capture"),
2118
2127
  unsafe_dubcolon,
2119
2128
  at_least_two=True,
2120
2129
  )("iter")
@@ -2306,7 +2315,7 @@ class Grammar(object):
2306
2315
  with_stmt = Forward()
2307
2316
 
2308
2317
  funcname_typeparams = Forward()
2309
- funcname_typeparams_tokens = dotted_setname + Optional(type_params)
2318
+ funcname_typeparams_tokens = dotted_outer_setname + Optional(type_params)
2310
2319
  name_funcdef = condense(funcname_typeparams + parameters)
2311
2320
  op_tfpdef = unsafe_typedef_default | condense(setname + Optional(default))
2312
2321
  op_funcdef_arg = setname | condense(lparen.suppress() + op_tfpdef + rparen.suppress())
@@ -772,7 +772,7 @@ def _coconut_trollius_coroutine(func):
772
772
  def raise_import_error(*args, **kwargs):
773
773
  raise err
774
774
  return raise_import_error
775
- asyncio.coroutine = _coconut_trollius_coroutine
775
+ types.ModuleType.__setattr__(asyncio, "coroutine", _coconut_trollius_coroutine)
776
776
  asyncio_Return = _coconut_lazy_module("trollius", attr="Return")
777
777
  ''',
778
778
  if_ge='''
@@ -812,6 +812,8 @@ if {assign_to} is _coconut_sentinel:
812
812
  if len(seq_groups) == 3:
813
813
  (front_gtype, front_match), mid_group, (back_gtype, back_match) = seq_groups
814
814
  internal_assert(front_gtype == "capture" == back_gtype, "invalid sequence match middle groups", seq_groups)
815
+ if "paren" in front_match or "paren" in back_match:
816
+ raise CoconutDeferredSyntaxError("parenthesized match patterns cannot be used in sequence search patterns", self.loc)
815
817
  mid_gtype, mid_contents = mid_group
816
818
 
817
819
  if iter_match:
@@ -119,6 +119,7 @@ py_version_str = sys.version.split()[0]
119
119
  use_fast_pyparsing_reprs = get_bool_env_var("COCONUT_FAST_PYPARSING_REPRS", True)
120
120
 
121
121
  warn_on_multiline_regex = False
122
+ warn_on_undefined_ParseResults_name = False
122
123
 
123
124
  default_whitespace_chars = " \t\f" # the only non-newline whitespace Python allows
124
125
 
@@ -844,6 +845,7 @@ coconut_specific_builtins = (
844
845
  "and_then",
845
846
  "and_then_await",
846
847
  "async_map",
848
+ "py_bytes",
847
849
  "py_chr",
848
850
  "py_dict",
849
851
  "py_hex",
@@ -26,7 +26,7 @@ import sys as _coconut_sys
26
26
  VERSION = "3.2.0"
27
27
  VERSION_NAME = None
28
28
  # False for release, int >= 1 for develop
29
- DEVELOP = 13
29
+ DEVELOP = 15
30
30
  ALPHA = False # for pre releases rather than post releases
31
31
 
32
32
  assert DEVELOP is False or DEVELOP >= 1, "DEVELOP must be False or an int >= 1"
@@ -445,10 +445,10 @@ def primary_test_1() -> bool:
445
445
  price = 100
446
446
  assert 0 == (requested_quantity ?? default_quantity) * price
447
447
  assert range(10) |> .[1] .. .[1:] == 2 == range(10) |> .[1:] |> .[1]
448
- assert None?.herp(derp) is None # type: ignore
449
- assert None?[herp].derp is None # type: ignore
450
- assert None?(derp)[herp] is None # type: ignore
451
- assert None?$(herp)(derp) is None # type: ignore
448
+ assert None?.herp(derp) is None # type: ignore # NOQA
449
+ assert None?[herp].derp is None # type: ignore # NOQA
450
+ assert None?(derp)[herp] is None # type: ignore # NOQA
451
+ assert None?$(herp)(derp) is None # type: ignore # NOQA
452
452
  a: int[]? = None # type: ignore
453
453
  assert a is None
454
454
  assert range(5) |> iter |> reiterable |> .[1] == 1
@@ -962,7 +962,7 @@ def primary_test_1() -> bool:
962
962
  def ret_abc():
963
963
  return "abc"
964
964
  )
965
- assert ret_abc() == "abc"
965
+ assert ret_abc() == "abc" # NOQA
966
966
  assert """" """ == '" '
967
967
  assert "" == """"""
968
968
  assert (,)(*(1, 2), 3) == (1, 2, 3)
@@ -732,6 +732,22 @@ def primary_test_2() -> bool:
732
732
  assert isinstance(lazy_d, lazy_deque)
733
733
  assert issubclass(type(lazy_d), lazy_deque)
734
734
 
735
+ # Issue #882: parenthesized match patterns in string captures
736
+ (int -> n882) + "G" = "8G"
737
+ assert n882 == 8
738
+ "G" + (int -> n882b) = "G8"
739
+ assert n882b == 8
740
+ "[" + (int -> n882c) + "]" = "[42]"
741
+ assert n882c == 42
742
+
743
+ a882 = "hello"
744
+ (==a882) + " world" = "hello world"
745
+
746
+ match (int -> mv882) + "px" in "100px":
747
+ assert mv882 == 100
748
+ else:
749
+ assert False
750
+
735
751
  # deferred ImportError on lazy imports
736
752
  lazy import coconut_nonexistent_test_module
737
753
  assert_raises(=> coconut_nonexistent_test_module.attr, ImportError)
@@ -143,6 +143,10 @@ def test_setup_none() -> bool:
143
143
  assert_raises(-> parse("def f(x) = return x"), CoconutSyntaxError)
144
144
  assert_raises(-> parse("def f(x) =\n return x"), CoconutSyntaxError)
145
145
  assert_raises(-> parse("10 20"), CoconutSyntaxError)
146
+ assert_raises(-> parse('x + "1" + (=="2") = "test"'), CoconutSyntaxError)
147
+ assert_raises(-> parse('(==x) + "1" + y = "test"'), CoconutSyntaxError)
148
+ assert_raises(-> parse(r'd"\n hello"'), CoconutSyntaxError)
149
+ assert_raises(-> parse('d""" hello"""'), CoconutSyntaxError)
146
150
 
147
151
  assert_raises(-> parse("()[(())"), CoconutSyntaxError, err_has="""
148
152
  unclosed open '[' (line 1)
@@ -433,7 +437,7 @@ line 6''')
433
437
  3
434
438
  """)
435
439
  except CoconutStyleError as err:
436
- assert str(err) == """found undefined name 'x' (add '# NOQA' to suppress) (remove --strict to downgrade to a warning) (line 2)
440
+ assert str(err) == """found undefined name 'x' (use explicit '\\x' syntax to bypass) (add '# NOQA' to suppress) (remove --strict to downgrade to a warning) (line 2)
437
441
  2 + x
438
442
  ^"""
439
443
  assert_raises(-> parse(d"""
@@ -456,32 +460,58 @@ line 6''')
456
460
  """), CoconutStyleError, **(dict(err_has="\n ...\n") if not PYPY else {}))
457
461
  assert_raises(-> parse('["abc", "def" "ghi"]'), CoconutStyleError, err_has="implicit string concatenation")
458
462
 
463
+ # scope-aware undefined name tests
464
+ assert_raises(-> parse(d"""
465
+ def f():
466
+ x = 1
467
+ y = x
468
+ """), CoconutStyleError, err_has="undefined name")
469
+ assert_raises(-> parse(d"""
470
+ def f():
471
+ x = 1
472
+ def g():
473
+ y = x
474
+ """), CoconutStyleError, err_has="undefined name")
475
+ assert parse(d"""
476
+ x = 1
477
+ def f():
478
+ y = x
479
+ """)
480
+ assert parse(d"""
481
+ x = 1
482
+ y = x
483
+ """)
484
+ assert parse(d"""
485
+ import abc
486
+ abc.ABCMeta
487
+ """)
488
+
459
489
  # imported name shadowing tests
460
490
  assert_raises(-> parse(d"""
461
491
  import abc
462
492
  abc: int = 1
463
- """), CoconutStyleError, err_has="shadows imported name")
493
+ """), CoconutStyleError, err_has="shadows import")
464
494
  assert_raises(-> parse(d"""
465
495
  from os import path
466
496
  path = 'test'
467
- """), CoconutStyleError, err_has="shadows imported name")
497
+ """), CoconutStyleError, err_has="shadows import")
468
498
  assert_raises(-> parse(d"""
469
499
  from collections import OrderedDict
470
500
  def OrderedDict() = None
471
- """), CoconutStyleError, err_has="shadows imported name")
501
+ """), CoconutStyleError, err_has="shadows import")
472
502
  assert_raises(-> parse(d"""
473
503
  import abc
474
504
  class abc:
475
505
  pass
476
- """), CoconutStyleError, err_has="shadows imported name")
506
+ """), CoconutStyleError, err_has="shadows import")
477
507
  assert_raises(-> parse(d"""
478
508
  from os import path
479
509
  [path for path in []]
480
- """), CoconutStyleError, err_has="shadows imported name")
510
+ """), CoconutStyleError, err_has="shadows import")
481
511
  assert_raises(-> parse(d"""
482
512
  import abc
483
513
  final abc = 1
484
- """), CoconutStyleError, err_has="shadows imported name")
514
+ """), CoconutStyleError, err_has="shadows import")
485
515
  # class attribute shadowing should be allowed (not module-level shadowing)
486
516
  # (use NOQA to suppress unused import warning since we're intentionally shadowing)
487
517
  assert parse(d"""
@@ -501,7 +531,7 @@ line 6''')
501
531
  class Foo:
502
532
  def bar(self):
503
533
  abc = 1
504
- """), CoconutStyleError, err_has="shadows imported name")
534
+ """), CoconutStyleError, err_has="shadows import")
505
535
  assert_raises(-> parse(d"""
506
536
  class Foo:
507
537
  def bar(self):
@@ -545,7 +575,7 @@ line 6''')
545
575
  def foo(self):
546
576
  class abc:
547
577
  pass
548
- """), CoconutStyleError, err_has="shadows imported name")
578
+ """), CoconutStyleError, err_has="shadows import")
549
579
  # deeply nested: class inside class inside method - no error because super is a class attribute of B
550
580
  assert parse(d"""
551
581
  class A: