From 821d19a833ce2320bd77799a039d84397bcc6e79 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 14 Feb 2017 18:20:25 -0800 Subject: [PATCH 001/176] Switches back to develop --- coconut/root.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/coconut/root.py b/coconut/root.py index 3c3664e32..f311ee77b 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -25,7 +25,8 @@ VERSION = "1.2.1" VERSION_NAME = "Colonel" -DEVELOP = False +# False for release, int >= 1 for develop +DEVELOP = 1 #----------------------------------------------------------------------------------------------------------------------- # CONSTANTS: From d641f98313b627666d1764ef9924bc2b6bd3a74d Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 14 Feb 2017 19:50:36 -0800 Subject: [PATCH 002/176] Moves to new MyPy version --- coconut/command/command.py | 30 ++++++++++++++++------------ coconut/command/mypy.py | 41 ++++++++++++++++++++++++++++++++++++++ coconut/command/util.py | 2 +- coconut/constants.py | 8 ++------ coconut/requirements.py | 3 --- coconut/terminal.py | 8 -------- 6 files changed, 61 insertions(+), 31 deletions(-) create mode 100644 coconut/command/mypy.py diff --git a/coconut/command/command.py b/coconut/command/command.py index cb9130522..3d0e55655 100644 --- a/coconut/command/command.py +++ b/coconut/command/command.py @@ -32,7 +32,7 @@ CoconutException, CoconutInternalException, ) -from coconut.terminal import logger +from coconut.terminal import logger, printerr from coconut.constants import ( fixpath, code_exts, @@ -431,7 +431,7 @@ def get_input(self, more=False): try: return self.prompt.input(more) except KeyboardInterrupt: - logger.printerr("\nKeyboardInterrupt") + printerr("\nKeyboardInterrupt") except EOFError: print() self.exit_runner() @@ -445,8 +445,8 @@ def start_running(self): def start_prompt(self): """Starts the interpreter.""" - logger.print("Coconut Interpreter:") - logger.print('(type "exit()" or press Ctrl-D to end)') + print("Coconut Interpreter:") + print('(type "exit()" or press Ctrl-D to end)') self.start_running() while self.running: code = self.get_input() @@ -546,14 +546,18 @@ def run_mypy(self, paths=[], code=None): """Run MyPy with arguments.""" set_mypy_path(stub_dir) if self.mypy: - args = ["python3", "-m", "mypy"] + paths + self.mypy_args - if code is None: - run_cmd(args, raise_errs=False) - else: - for err in run_cmd(args + ["-c", code], show_output=False, raise_errs=False).splitlines(): - if err not in self.mypy_errs: - logger.printerr(err) - self.mypy_errs.append(err) + from coconut.command.mypy import mypy_run + args = paths + self.mypy_args + if code is not None: + args += ["-c", code] + for line, is_err in mypy_run(args): + if code is None or line not in self.mypy_errs: + if is_err: + printerr(line) + else: + print(line) + if line not in self.mypy_errs: + self.mypy_errs.append(line) def start_jupyter(self, args): """Starts Jupyter with the Coconut kernel.""" @@ -600,7 +604,7 @@ def watch(self, source, write=True, package=None, run=False, force=False): source = fixpath(source) - logger.print() + print() logger.show_tabulated("Watching", showpath(source), "(press Ctrl-C to end)...") def recompile(path): diff --git a/coconut/command/mypy.py b/coconut/command/mypy.py new file mode 100644 index 000000000..820c9f0da --- /dev/null +++ b/coconut/command/mypy.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +#----------------------------------------------------------------------------------------------------------------------- +# INFO: +#----------------------------------------------------------------------------------------------------------------------- + +""" +Authors: Evan Hubinger +License: Apache 2.0 +Description: Handles interfacing with MyPy to make --mypy work. +""" + +#----------------------------------------------------------------------------------------------------------------------- +# IMPORTS: +#----------------------------------------------------------------------------------------------------------------------- + +from __future__ import print_function, absolute_import, unicode_literals, division + +from coconut.root import * # NOQA + +from coconut.exceptions import CoconutException + +try: + from mypy import api +except ImportError: + raise CoconutException("--mypy flag requires MyPy library", + extra="run 'pip install coconut[mypy]' to fix") + +#----------------------------------------------------------------------------------------------------------------------- +# CLASSES: +#----------------------------------------------------------------------------------------------------------------------- + + +def mypy_run(args): + """Runs mypy with given arguments and shows the result.""" + stdout_results, stderr_results = api.run(args) + for line in stdout_results.splitlines(): + yield line, False + for line in stderr_results.splitlines(): + yield line, True diff --git a/coconut/command/util.py b/coconut/command/util.py index 964a0c3a9..fe180f2c8 100644 --- a/coconut/command/util.py +++ b/coconut/command/util.py @@ -276,7 +276,7 @@ def set_style(self, style): elif prompt_toolkit is None: raise CoconutException("syntax highlighting is not supported on this Python version") elif style == "list": - logger.print("Coconut Styles: none, " + ", ".join(pygments.styles.get_all_styles())) + print("Coconut Styles: none, " + ", ".join(pygments.styles.get_all_styles())) sys.exit(0) elif style in pygments.styles.get_all_styles(): self.style = style diff --git a/coconut/constants.py b/coconut/constants.py index 5274d6d50..01d2b3201 100644 --- a/coconut/constants.py +++ b/coconut/constants.py @@ -60,10 +60,7 @@ def fixpath(path): "ipython", ], "mypy": [ - "mypy-lang", - ], - "typed-ast": [ - "typed_ast", + "mypy", ], "watch": [ "watchdog", @@ -94,12 +91,11 @@ def fixpath(path): "jupyter": (1, 0), "jupyter-console": (5, 1), "ipython": (5, 2), - "mypy-lang": (0, 4), + "mypy": (0, 470), "prompt_toolkit": (1, 0), "futures": (3, 0), "argparse": (1, 4), "pytest": (3, 0), - "typed_ast": (1, 0), "watchdog": (0, 8), "requests": (2, 13), } diff --git a/coconut/requirements.py b/coconut/requirements.py index 714b3bdb9..2820ad1db 100644 --- a/coconut/requirements.py +++ b/coconut/requirements.py @@ -78,9 +78,6 @@ def everything_in(req_dict): "mypy": get_reqs("mypy"), } -if sys.version_info >= (3, 3) and platform.system() != "Windows": - extras["mypy"] += get_reqs("typed-ast") - extras["ipython"] = extras["jupyter"] extras["all"] = everything_in(extras) diff --git a/coconut/terminal.py b/coconut/terminal.py index 14059fcb1..48cad5002 100644 --- a/coconut/terminal.py +++ b/coconut/terminal.py @@ -105,14 +105,6 @@ def display(self, messages, sig="", debug=False): else: print(full_message) - def print(self, *messages): - """Prints messages.""" - self.display(messages) - - def printerr(self, *messages): - """Prints error messages with debug signature.""" - self.display(messages, debug=True) - def show(self, *messages): """Prints messages with main signature.""" if not self.quiet: From 1e5290c796c316312c8eef44f2673fce6ee3bd59 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 14 Feb 2017 20:51:18 -0800 Subject: [PATCH 003/176] Improves mypy api usage --- Makefile | 10 ++++++++++ coconut/command/mypy.py | 31 +++++++++++++++++++++++++------ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 66844413e..b83582f93 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,16 @@ clean: find . -name '*.pyc' -delete find . -name '__pycache__' -delete +.PHONY: wipe +wipe: clean + -pip uninstall coconut + -pip uninstall coconut-develop + -pip3 uninstall coconut + -pip3 uninstall coconut-develop + -pip2 uninstall coconut + -pip2 uninstall coconut-develop + rm -rf *.egg-info + .PHONY: build build: clean pip3 install --upgrade setuptools diff --git a/coconut/command/mypy.py b/coconut/command/mypy.py index 820c9f0da..21f6ca28c 100644 --- a/coconut/command/mypy.py +++ b/coconut/command/mypy.py @@ -19,10 +19,17 @@ from coconut.root import * # NOQA +import sys +import traceback +try: + from io import StringIO +except ImportError: + from StringIO import StringIO + from coconut.exceptions import CoconutException try: - from mypy import api + from mypy.main import main except ImportError: raise CoconutException("--mypy flag requires MyPy library", extra="run 'pip install coconut[mypy]' to fix") @@ -34,8 +41,20 @@ def mypy_run(args): """Runs mypy with given arguments and shows the result.""" - stdout_results, stderr_results = api.run(args) - for line in stdout_results.splitlines(): - yield line, False - for line in stderr_results.splitlines(): - yield line, True + argv, sys.argv = sys.argv, args + stdout, sys.stdout = sys.stdout, StringIO() + stderr, sys.stderr = sys.stderr, StringIO() + + try: + main(None) + except: + traceback.print_exc() + + out = [] + for line in sys.stdout.getvalue().splitlines(): + out.append((line, False)) + for line in sys.stderr.getvalue().splitlines(): + out.append((line, True)) + + sys.argv, sys.stdout, sys.stderr = argv, stdout, stderr + return out From 1e0260ceb160c5c90c6fa8421713522964b7c947 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 14 Feb 2017 22:31:11 -0800 Subject: [PATCH 004/176] Fixes invalid method calls --- coconut/terminal.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/coconut/terminal.py b/coconut/terminal.py index 48cad5002..3a7d88d93 100644 --- a/coconut/terminal.py +++ b/coconut/terminal.py @@ -118,7 +118,7 @@ def show_error(self, *messages): def log(self, *messages): """Logs debug messages if in verbose mode.""" if self.verbose: - self.printerr(*messages) + printerr(*messages) def log_show(self, *messages): """Logs debug messages with main signature.""" @@ -168,7 +168,7 @@ def print_exc(self): line = " " * taberrfmt + line errmsg_lines.append(line) errmsg = "\n".join(errmsg_lines) - self.printerr(errmsg) + printerr(errmsg) def log_cmd(self, args): """Logs a console command if in verbose mode.""" @@ -186,9 +186,9 @@ def log_tag(self, tag, code, multiline=False): if self.tracing: tagstr = "[" + str(tag) + "]" if multiline: - self.printerr(tagstr + "\n" + debug_clean(code)) + printerr(tagstr + "\n" + debug_clean(code)) else: - self.printerr(tagstr, ascii(code)) + printerr(tagstr, ascii(code)) def log_trace(self, tag, original, loc, tokens=None): """Formats and displays a trace if tracing.""" @@ -205,7 +205,7 @@ def log_trace(self, tag, original, loc, tokens=None): else: out.append(str(tokens)) out.append("(line " + str(lineno(loc, original)) + ", col " + str(col(loc, original)) + ")") - self.printerr(*out) + printerr(*out) def _trace_start_action(self, original, loc, expr): self.log_trace(expr, original, loc) @@ -252,10 +252,10 @@ def gather_parsing_stats(self): yield finally: elapsed_time = time.clock() - start_time - self.printerr("Time while parsing:", elapsed_time, "seconds") + printerr("Time while parsing:", elapsed_time, "seconds") if use_packrat: hits, misses = ParserElement.packrat_cache_stats - self.printerr("Packrat parsing stats:", hits, "hits;", misses, "misses") + printerr("Packrat parsing stats:", hits, "hits;", misses, "misses") else: yield @@ -271,7 +271,7 @@ def getLogger(name=None): def pylog(self, *args, **kwargs): """Display all available logging information.""" - self.printerr(self.name, args, kwargs, traceback.format_exc()) + printerr(self.name, args, kwargs, traceback.format_exc()) debug = info = warning = error = critical = exception = pylog From 44f400295adc3ba929f9c8335a45f83498d7b632 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 14 Feb 2017 22:48:28 -0800 Subject: [PATCH 005/176] Fixes new mypy support --- coconut/command/mypy.py | 2 +- tests/main_test.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/coconut/command/mypy.py b/coconut/command/mypy.py index 21f6ca28c..9923ffcc3 100644 --- a/coconut/command/mypy.py +++ b/coconut/command/mypy.py @@ -41,7 +41,7 @@ def mypy_run(args): """Runs mypy with given arguments and shows the result.""" - argv, sys.argv = sys.argv, args + argv, sys.argv = sys.argv, [""] + args stdout, sys.stdout = sys.stdout, StringIO() stderr, sys.stderr = sys.stderr, StringIO() diff --git a/tests/main_test.py b/tests/main_test.py index a33cb924f..7aee85f38 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -63,6 +63,7 @@ def call(cmd, assert_output=False, **kwargs): line = None for raw_line in subprocess.Popen(cmd, stdout=subprocess.PIPE, **kwargs).stdout.readlines(): line = raw_line.rstrip().decode(sys.stdout.encoding) + assert "Traceback (most recent call last):" not in line print(line) if assert_output is None: assert line is None @@ -288,7 +289,7 @@ def test_minify(self): def test_strict(self): run(["--strict"]) - if not PY2: + if sys.version_info >= (3, 4): def test_mypy(self): run(["--mypy"]) From 16573c463d6fe7077f65a339d06e01faa1b7ea30 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 14 Feb 2017 23:10:59 -0800 Subject: [PATCH 006/176] Further fixes new mypy support --- CONTRIBUTING.md | 4 +++- coconut/command/mypy.py | 2 ++ coconut/constants.py | 2 +- tests/main_test.py | 31 +++++++++++++++---------------- 4 files changed, 21 insertions(+), 18 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ce1c4e901..d5a36d88c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -58,10 +58,12 @@ Contributing to Coconut is as simple as + Creates the `ArgumentParser` object used to parse Coconut command-line arguments. - `command.py` + Contains `Command`, whose `start` method is the main entry point for the Coconut command-line utility. + - `mypy.py` + + Contains objects necessary for Coconut's `--mypy` flag. - `util.py` + Contains utilities used by `command.py`, including `Prompt` for getting syntax-highlighted input, and `Runner` for executing compiled Python. - `watch.py` - + Contains classes necessary for Coconut's `--watch` flag. + + Contains objects necessary for Coconut's `--watch` flag. - compiler - `__init__.py` + Imports everything in `compiler.py`. diff --git a/coconut/command/mypy.py b/coconut/command/mypy.py index 9923ffcc3..e66d31530 100644 --- a/coconut/command/mypy.py +++ b/coconut/command/mypy.py @@ -47,6 +47,8 @@ def mypy_run(args): try: main(None) + except SystemExit: + pass except: traceback.print_exc() diff --git a/coconut/constants.py b/coconut/constants.py index 01d2b3201..f78766093 100644 --- a/coconut/constants.py +++ b/coconut/constants.py @@ -91,7 +91,7 @@ def fixpath(path): "jupyter": (1, 0), "jupyter-console": (5, 1), "ipython": (5, 2), - "mypy": (0, 470), + "mypy": (0, 471), "prompt_toolkit": (1, 0), "futures": (3, 0), "argparse": (1, 4), diff --git a/tests/main_test.py b/tests/main_test.py index 7aee85f38..87cefabde 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -59,28 +59,27 @@ def call(cmd, assert_output=False, **kwargs): if assert_output is True: assert_output = "" print("\n>", (cmd if isinstance(cmd, str) else " ".join(cmd))) - if assert_output is not False: - line = None - for raw_line in subprocess.Popen(cmd, stdout=subprocess.PIPE, **kwargs).stdout.readlines(): - line = raw_line.rstrip().decode(sys.stdout.encoding) - assert "Traceback (most recent call last):" not in line - print(line) - if assert_output is None: - assert line is None - else: - assert assert_output in line - else: - subprocess.check_call(cmd, **kwargs) + check_error = not any("extras" in arg for arg in cmd) + line = None + for raw_line in subprocess.Popen(cmd, stdout=subprocess.PIPE, **kwargs).stdout.readlines(): + line = raw_line.rstrip().decode(sys.stdout.encoding) + assert "Traceback (most recent call last):" not in line + if check_error: + assert "error:" not in line + assert "Exception" not in line + assert "Error" not in line + print(line) + if assert_output is None: + assert line is None + elif assert_output is not False: + assert assert_output in line def call_coconut(args): """Calls Coconut.""" if "--jobs" not in args and platform.python_implementation() != "PyPy": args = ["--jobs", "sys"] + args - if "--mypy" in args and not any("extras" in arg for arg in args): - call(["coconut"] + args, assert_output="Coconut:") - else: - call(["coconut"] + args) + call(["coconut"] + args) def comp(path=None, folder=None, file=None, args=[]): From b6788de3384f760492448a678a1a40213630eff4 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Wed, 15 Feb 2017 00:39:40 -0800 Subject: [PATCH 007/176] Fixes mypy requirements, tests --- coconut/requirements.py | 2 +- tests/main_test.py | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/coconut/requirements.py b/coconut/requirements.py index 2820ad1db..0f25b2f5a 100644 --- a/coconut/requirements.py +++ b/coconut/requirements.py @@ -86,7 +86,7 @@ def everything_in(req_dict): get_reqs("tests") + (extras["jobs"] if platform.python_implementation() != "PyPy" else []) + (extras["jupyter"] if (PY2 and not PY26) or sys.version_info >= (3, 3) else []) - + (extras["mypy"] if not PY2 else []) + + (extras["mypy"] if sys.version_info >= (3, 4) else []) ) extras["docs"] = unique_wrt(get_reqs("docs"), requirements) diff --git a/tests/main_test.py b/tests/main_test.py index 87cefabde..9e0df3be2 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -54,20 +54,21 @@ def escape(inputstring): return inputstring.replace("$", "\\$").replace("`", "\\`") -def call(cmd, assert_output=False, **kwargs): +def call(cmd, assert_output=False, check_mypy=None, ignore_mypy=("tutorial",), **kwargs): """Executes a shell command.""" if assert_output is True: assert_output = "" + if check_mypy is None: + check_mypy = all("extras" not in arg for arg in cmd) print("\n>", (cmd if isinstance(cmd, str) else " ".join(cmd))) - check_error = not any("extras" in arg for arg in cmd) line = None for raw_line in subprocess.Popen(cmd, stdout=subprocess.PIPE, **kwargs).stdout.readlines(): line = raw_line.rstrip().decode(sys.stdout.encoding) assert "Traceback (most recent call last):" not in line - if check_error: + assert "Exception" not in line + assert "Error" not in line + if check_mypy and all(test not in line for test in ignore_mypy): assert "error:" not in line - assert "Exception" not in line - assert "Error" not in line print(line) if assert_output is None: assert line is None From 2b3fc866e183162aac1f867429fd8a21e1472c41 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Wed, 15 Feb 2017 01:23:07 -0800 Subject: [PATCH 008/176] Fixes strings in comments --- coconut/compiler/compiler.py | 2 +- tests/main_test.py | 19 +++++++++++++------ tests/src/cocotest/agnostic/util.coco | 4 ++-- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/coconut/compiler/compiler.py b/coconut/compiler/compiler.py index e6b0b1676..960aed2a1 100644 --- a/coconut/compiler/compiler.py +++ b/coconut/compiler/compiler.py @@ -358,7 +358,7 @@ def wrap_passthrough(self, text, multiline=True): def wrap_comment(self, text): """Wraps a comment.""" - return "#" + self.add_ref(text) + unwrapper + return "#" + self.add_ref(self.reformat(text)) + unwrapper def wrap_line_number(self, ln): """Wraps a line number.""" diff --git a/tests/main_test.py b/tests/main_test.py index 9e0df3be2..8aadf6580 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -44,6 +44,11 @@ coconut_snip = r"msg = ''; pmsg = print$(msg); `pmsg`" +ignore_mypy_errs_with = ( + "already defined", + "cannot determine type of", +) + #----------------------------------------------------------------------------------------------------------------------- # UTILITIES: #----------------------------------------------------------------------------------------------------------------------- @@ -54,26 +59,28 @@ def escape(inputstring): return inputstring.replace("$", "\\$").replace("`", "\\`") -def call(cmd, assert_output=False, check_mypy=None, ignore_mypy=("tutorial",), **kwargs): +def call(cmd, assert_output=False, check_mypy=None, **kwargs): """Executes a shell command.""" if assert_output is True: assert_output = "" if check_mypy is None: check_mypy = all("extras" not in arg for arg in cmd) print("\n>", (cmd if isinstance(cmd, str) else " ".join(cmd))) - line = None + lines = [] for raw_line in subprocess.Popen(cmd, stdout=subprocess.PIPE, **kwargs).stdout.readlines(): line = raw_line.rstrip().decode(sys.stdout.encoding) + print(line) + lines.append(line) + for line in lines: assert "Traceback (most recent call last):" not in line assert "Exception" not in line assert "Error" not in line - if check_mypy and all(test not in line for test in ignore_mypy): + if check_mypy and all(test not in line for test in ignore_mypy_errs_with): assert "error:" not in line - print(line) if assert_output is None: - assert line is None + assert not lines elif assert_output is not False: - assert assert_output in line + assert lines and assert_output in lines[-1] def call_coconut(args): diff --git a/tests/src/cocotest/agnostic/util.coco b/tests/src/cocotest/agnostic/util.coco index 9bd11e00e..c9d04454a 100644 --- a/tests/src/cocotest/agnostic/util.coco +++ b/tests/src/cocotest/agnostic/util.coco @@ -576,9 +576,9 @@ import sys if sys.version_info > (3, 5): from typing import Any, List, Dict - def args_kwargs_func(args:List[Any]=[], kwargs:Dict[Any,Any]={}) -> None: pass + def args_kwargs_func(args:List[Any]=[], kwargs:Dict[Any, Any]={}) -> None: pass else: - def args_kwargs_func(args: [int]=[], kwargs: {"str": int}={}) -> None: pass + def args_kwargs_func(args:"List[Any]"=[], kwargs:"Dict[Any, Any]"={}) -> None: pass def anything_func(*args: int, **kwargs: int) -> None: pass From 25e28ee641c4d40fd5c42f34fd8240b67b7a09eb Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Wed, 15 Feb 2017 16:16:28 -0800 Subject: [PATCH 009/176] Fixes comment reformatting --- coconut/compiler/compiler.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/coconut/compiler/compiler.py b/coconut/compiler/compiler.py index 960aed2a1..44d01768f 100644 --- a/coconut/compiler/compiler.py +++ b/coconut/compiler/compiler.py @@ -356,9 +356,11 @@ def wrap_passthrough(self, text, multiline=True): out += "\n" return out - def wrap_comment(self, text): + def wrap_comment(self, text, reformat=True): """Wraps a comment.""" - return "#" + self.add_ref(self.reformat(text)) + unwrapper + if reformat: + text = self.reformat(text) + return "#" + self.add_ref(text) + unwrapper def wrap_line_number(self, ln): """Wraps a line number.""" @@ -496,7 +498,7 @@ def str_proc(self, inputstring, **kwargs): out = ["\n".join(lines)] out.append(c) else: - out.append(self.wrap_comment(hold[_comment]) + c) + out.append(self.wrap_comment(hold[_comment], reformat=False) + c) hold = None else: hold[_comment] += c @@ -775,7 +777,7 @@ def endline_comment(self, ln): comment = " line " + str(ln) else: raise CoconutInternalException("attempted to add line number comment without --line-numbers or --keep-lines") - return self.wrap_comment(comment) + return self.wrap_comment(comment, reformat=False) def endline_repl(self, inputstring, add_to_line=True, **kwargs): """Adds in end line comments.""" From a6e1621c918bd7cfe98a1eb944f548e55e37fb14 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Wed, 15 Feb 2017 19:50:04 -0800 Subject: [PATCH 010/176] Fixes more mypy errors --- coconut/stubs/__coconut__.pyi | 4 +--- tests/main_test.py | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/coconut/stubs/__coconut__.pyi b/coconut/stubs/__coconut__.pyi index 22f5108d8..5a698f1ee 100644 --- a/coconut/stubs/__coconut__.pyi +++ b/coconut/stubs/__coconut__.pyi @@ -91,9 +91,7 @@ def _coconut_igetitem( ) -> Iterable[_T]: ... -class _coconut_compose: - def __init__(self, *funcs: Callable) -> None: ... - def __call__(self, *args: Any, **kwargs: Any) -> Any: ... +def _coconut_compose(*funcs: Callable) -> Callable[..., Any]: ... def _coconut_pipe(x: _T, f: Callable[[_T], _S]) -> _S: ... diff --git a/tests/main_test.py b/tests/main_test.py index 8aadf6580..f6d2604bc 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -47,6 +47,7 @@ ignore_mypy_errs_with = ( "already defined", "cannot determine type of", + "decorator expected", ) #----------------------------------------------------------------------------------------------------------------------- @@ -297,9 +298,8 @@ def test_strict(self): run(["--strict"]) if sys.version_info >= (3, 4): - def test_mypy(self): - run(["--mypy"]) + run(["--mypy", "--ignore-missing-imports"]) class TestExternal(unittest.TestCase): From f5dc3a696cbac5ccaabfd43d60e2a8b70b3bca14 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Wed, 15 Feb 2017 20:13:35 -0800 Subject: [PATCH 011/176] Further improves stub file --- coconut/stubs/__coconut__.pyi | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/coconut/stubs/__coconut__.pyi b/coconut/stubs/__coconut__.pyi index 5a698f1ee..3d6a3f72d 100644 --- a/coconut/stubs/__coconut__.pyi +++ b/coconut/stubs/__coconut__.pyi @@ -24,7 +24,7 @@ if sys.version_info >= (3,): else: import __builtin__ as _b - from future_builtins import * # type: ignore + from future_builtins import * from io import open py_raw_input, py_xrange = _b.raw_input, _b.xrange @@ -91,7 +91,9 @@ def _coconut_igetitem( ) -> Iterable[_T]: ... -def _coconut_compose(*funcs: Callable) -> Callable[..., Any]: ... +class _coconut_compose: + def __init__(self, *funcs: Any) -> None: ... + def __call__(self, *args: Any, **kwargs: Any) -> Any: ... def _coconut_pipe(x: _T, f: Callable[[_T], _S]) -> _S: ... From deedec32f03cef9975a9994977cabd5b1b484a09 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Wed, 15 Feb 2017 20:38:32 -0800 Subject: [PATCH 012/176] Attempts to fix more mypy errors --- coconut/stubs/__coconut__.pyi | 12 ++++++++---- tests/main_test.py | 21 +++++++++++---------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/coconut/stubs/__coconut__.pyi b/coconut/stubs/__coconut__.pyi index 3d6a3f72d..9bde608d5 100644 --- a/coconut/stubs/__coconut__.pyi +++ b/coconut/stubs/__coconut__.pyi @@ -104,14 +104,18 @@ def _coconut_starpipe(xs: Iterable, f: Callable[..., _T]) -> _T: ... def _coconut_backstarpipe(f: Callable[..., _T], xs: Iterable) -> _T: ... -def _coconut_bool_and(a: Any, b: Any) -> bool: ... -def _coconut_bool_or(a: Any, b: Any) -> bool: ... +def _coconut_bool_and(a, b) -> bool: + return a and b +def _coconut_bool_or(a, b) -> bool: + return a or b @overload -def _coconut_minus(a: _T) -> _T: ... +def _coconut_minus(a): + return -a @overload -def _coconut_minus(a: _T, b: _S) -> Union[_T, _S]: ... +def _coconut_minus(a, b): + return a - b class count: diff --git a/tests/main_test.py b/tests/main_test.py index f6d2604bc..428613b45 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -45,6 +45,7 @@ coconut_snip = r"msg = ''; pmsg = print$(msg); `pmsg`" ignore_mypy_errs_with = ( + "tutorial", "already defined", "cannot determine type of", "decorator expected", @@ -276,15 +277,22 @@ def test_target(self): def test_jobs_zero(self): run(["--jobs", "0"]) + if sys.version_info >= (3, 4): + def test_mypy(self): + run(["--mypy", "--ignore-missing-imports"]) + + def test_strict(self): + run(["--strict"]) + def test_run(self): run(use_run_arg=True) - def test_standalone(self): - run(["--standalone"]) - def test_package(self): run(["--package"]) + def test_standalone(self): + run(["--standalone"]) + def test_line_numbers(self): run(["--linenumbers"]) @@ -294,13 +302,6 @@ def test_keep_lines(self): def test_minify(self): run(["--minify"]) - def test_strict(self): - run(["--strict"]) - - if sys.version_info >= (3, 4): - def test_mypy(self): - run(["--mypy", "--ignore-missing-imports"]) - class TestExternal(unittest.TestCase): From d2340428578f76b868ffba34023ca03f96f01aca Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Wed, 15 Feb 2017 21:26:57 -0800 Subject: [PATCH 013/176] Ignores compose and partial mypy errors --- tests/main_test.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/main_test.py b/tests/main_test.py index 428613b45..c1d9cb0fc 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -49,6 +49,8 @@ "already defined", "cannot determine type of", "decorator expected", + "coconut_compose_", + "coconut_partial_", ) #----------------------------------------------------------------------------------------------------------------------- From ea083f3dd9e4be006e5e95c4a48b1f863fbeca4c Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Thu, 16 Feb 2017 00:46:00 -0800 Subject: [PATCH 014/176] Fixes ignoring mypy errors --- tests/main_test.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/main_test.py b/tests/main_test.py index c1d9cb0fc..6bd5fe0c7 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -45,12 +45,12 @@ coconut_snip = r"msg = ''; pmsg = print$(msg); `pmsg`" ignore_mypy_errs_with = ( - "tutorial", "already defined", "cannot determine type of", "decorator expected", - "coconut_compose_", - "coconut_partial_", + "tutorial", + "_coconut_compose", + "_coconut_partial", ) #----------------------------------------------------------------------------------------------------------------------- From 478b3b183be42fa3408596a4294413d88b8289fe Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Thu, 16 Feb 2017 14:43:51 -0800 Subject: [PATCH 015/176] Fixes capitalization --- tests/main_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/main_test.py b/tests/main_test.py index 6bd5fe0c7..14b459f4e 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -46,7 +46,7 @@ ignore_mypy_errs_with = ( "already defined", - "cannot determine type of", + "Cannot determine type of", "decorator expected", "tutorial", "_coconut_compose", From acc5420a7a4c00306d7c09e98765fe54c9de7413 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Thu, 16 Feb 2017 15:46:02 -0800 Subject: [PATCH 016/176] Fixes string types --- tests/src/cocotest/agnostic/util.coco | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/cocotest/agnostic/util.coco b/tests/src/cocotest/agnostic/util.coco index c9d04454a..301407764 100644 --- a/tests/src/cocotest/agnostic/util.coco +++ b/tests/src/cocotest/agnostic/util.coco @@ -578,7 +578,7 @@ if sys.version_info > (3, 5): def args_kwargs_func(args:List[Any]=[], kwargs:Dict[Any, Any]={}) -> None: pass else: - def args_kwargs_func(args:"List[Any]"=[], kwargs:"Dict[Any, Any]"={}) -> None: pass + def args_kwargs_func(args:"List"=[], kwargs:"Dict"={}) -> None: pass def anything_func(*args: int, **kwargs: int) -> None: pass From 3ef1259e794daa6f9300fd4f9dd370168a703f1e Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 18 Feb 2017 14:00:14 -0800 Subject: [PATCH 017/176] Attempts to fix dependency errors Refs #219. --- Makefile | 6 +++--- coconut/command/util.py | 22 +++++++++++++--------- coconut/requirements.py | 25 +++++++++++++++++-------- 3 files changed, 33 insertions(+), 20 deletions(-) diff --git a/Makefile b/Makefile index b83582f93..0fd06a3e8 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,12 @@ .PHONY: install install: + pip install "setuptools>=18.8.1" pip install "pip>=7.1.2" pip install .[tests] .PHONY: dev dev: - pip3 install --upgrade pip + pip3 install --upgrade setuptools pip pip3 install --upgrade -e .[dev] pre-commit install -f --install-hooks @@ -41,8 +42,7 @@ wipe: clean rm -rf *.egg-info .PHONY: build -build: clean - pip3 install --upgrade setuptools +build: clean dev python3 setup.py sdist bdist_wheel .PHONY: upload diff --git a/coconut/command/util.py b/coconut/command/util.py index fe180f2c8..3feea993e 100644 --- a/coconut/command/util.py +++ b/coconut/command/util.py @@ -179,16 +179,20 @@ def handling_broken_process_pool(): def kill_children(): """Terminates all child processes.""" - import psutil - master = psutil.Process() - children = master.children(recursive=True) - while children: - for child in children: - try: - child.terminate() - except psutil.NoSuchProcess: - pass # process is already dead, so do nothing + try: + import psutil + except ImportError: + logger.warn("missing psutil; --jobs may not properly terminate", extra="run 'pip install coconut[jobs]' to fix") + else: + master = psutil.Process() children = master.children(recursive=True) + while children: + for child in children: + try: + child.terminate() + except psutil.NoSuchProcess: + pass # process is already dead, so do nothing + children = master.children(recursive=True) def splitname(path): diff --git a/coconut/requirements.py b/coconut/requirements.py index 0f25b2f5a..a42e66dd1 100644 --- a/coconut/requirements.py +++ b/coconut/requirements.py @@ -20,6 +20,9 @@ import sys import platform +import setuptools + +from coconut.exceptions import CoconutException from coconut.constants import all_reqs, req_vers #----------------------------------------------------------------------------------------------------------------------- @@ -63,14 +66,6 @@ def everything_in(req_dict): requirements = get_reqs() -if PY26: - requirements += get_reqs("py26") -else: - requirements += get_reqs("non-py26") - -if PY2: - requirements += get_reqs("py2") - extras = { "jupyter": get_reqs("jupyter"), "watch": get_reqs("watch"), @@ -96,6 +91,20 @@ def everything_in(req_dict): + get_reqs("dev") ) +if int(setuptools.__version__.split(".", 1)[0]) < 18: + if "bdist_wheel" in sys.argv: + raise CoconutException("bdist_wheel not supported for setuptools versions < 18", extra="run 'pip install --upgrade setuptools' to fix") + if PY26: + requirements += get_reqs("py26") + else: + requirements += get_reqs("non-py26") + if PY2: + requirements += get_reqs("py2") +else: + extras[":python_version<'2.7'"] = get_reqs("py26") + extras[":python_version>='2.7'"] = get_reqs("non-py26") + extras[":python_version<'3'"] = get_reqs("py2") + #----------------------------------------------------------------------------------------------------------------------- # MAIN: #----------------------------------------------------------------------------------------------------------------------- From 66e0371d7607011a2a77bd14aeb2153d8880349e Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 18 Feb 2017 14:08:00 -0800 Subject: [PATCH 018/176] Attempts to improve jupyter kernel performance Refs #221. --- coconut/icoconut/root.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/coconut/icoconut/root.py b/coconut/icoconut/root.py index 58a94a744..68add520c 100644 --- a/coconut/icoconut/root.py +++ b/coconut/icoconut/root.py @@ -44,6 +44,19 @@ COMPILER = Compiler(target="sys") RUNNER = Runner(COMPILER) +parse_block_memo = {} + + +def memoized_parse_block(code): + """Memoized version of parse_block.""" + if code in parse_block_memo: + return parse_block_memo[code] + else: + parsed = COMPILER.parse_block(code) + parse_block_memo[code] = parsed + return parsed + + #----------------------------------------------------------------------------------------------------------------------- # KERNEL: #----------------------------------------------------------------------------------------------------------------------- @@ -60,7 +73,7 @@ def __init__(self, *args, **kwargs): def ast_parse(self, source, *args, **kwargs): """Version of ast_parse that compiles Coconut code first.""" try: - compiled = COMPILER.parse_block(source) + compiled = memoized_parse_block(source) except CoconutException as err: raise err.syntax_err() else: @@ -78,7 +91,7 @@ def __init__(self, *args, **kwargs): def _coconut_compile(self, source, *args, **kwargs): """Version of _compile that compiles Coconut code first.""" try: - compiled = COMPILER.parse_block(source) + compiled = memoized_parse_block(source) except CoconutException as err: if source.endswith("\n\n"): raise err.syntax_err() From 801f996912ff836572bea95e92475f94b98380c5 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 18 Feb 2017 14:14:10 -0800 Subject: [PATCH 019/176] Fixes error memoization --- coconut/icoconut/root.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/coconut/icoconut/root.py b/coconut/icoconut/root.py index 68add520c..895aeee38 100644 --- a/coconut/icoconut/root.py +++ b/coconut/icoconut/root.py @@ -50,11 +50,19 @@ def memoized_parse_block(code): """Memoized version of parse_block.""" if code in parse_block_memo: - return parse_block_memo[code] + result = parse_block_memo[code] else: - parsed = COMPILER.parse_block(code) - parse_block_memo[code] = parsed - return parsed + try: + parsed = COMPILER.parse_block(code) + except Exception as err: + result = err + else: + result = parsed + parse_block_memo[code] = result + if isinstance(result, Exception): + raise result + else: + return result #----------------------------------------------------------------------------------------------------------------------- From 55f239c720768186c5c3d04671b142ad14c3c06c Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 18 Feb 2017 14:24:07 -0800 Subject: [PATCH 020/176] Removes a potentially broken import --- coconut/requirements.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/coconut/requirements.py b/coconut/requirements.py index a42e66dd1..25b8932fe 100644 --- a/coconut/requirements.py +++ b/coconut/requirements.py @@ -22,7 +22,6 @@ import setuptools -from coconut.exceptions import CoconutException from coconut.constants import all_reqs, req_vers #----------------------------------------------------------------------------------------------------------------------- @@ -93,7 +92,7 @@ def everything_in(req_dict): if int(setuptools.__version__.split(".", 1)[0]) < 18: if "bdist_wheel" in sys.argv: - raise CoconutException("bdist_wheel not supported for setuptools versions < 18", extra="run 'pip install --upgrade setuptools' to fix") + raise RuntimeError("bdist_wheel not supported for setuptools versions < 18 (run 'pip install --upgrade setuptools' to fix)") if PY26: requirements += get_reqs("py26") else: From ac6eefb2f9cff9e690a39709728a9ecba8a279c5 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 18 Feb 2017 14:29:51 -0800 Subject: [PATCH 021/176] Bumps develop version --- coconut/root.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coconut/root.py b/coconut/root.py index f311ee77b..2ff9859e0 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -26,7 +26,7 @@ VERSION = "1.2.1" VERSION_NAME = "Colonel" # False for release, int >= 1 for develop -DEVELOP = 1 +DEVELOP = 2 #----------------------------------------------------------------------------------------------------------------------- # CONSTANTS: From 48c3aecdde5605da49e38707220055819ad5d5d7 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 18 Feb 2017 14:58:37 -0800 Subject: [PATCH 022/176] Removes makefile setuptools upgrade --- Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/Makefile b/Makefile index 0fd06a3e8..c60ca7938 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,5 @@ .PHONY: install install: - pip install "setuptools>=18.8.1" pip install "pip>=7.1.2" pip install .[tests] From f3adae53bb6c21240e02154b14c286003d909883 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 18 Feb 2017 19:10:01 -0800 Subject: [PATCH 023/176] Prevents repetitive jupyter console compilation Refs #221. --- coconut/command/command.py | 2 +- coconut/icoconut/root.py | 21 ++++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/coconut/command/command.py b/coconut/command/command.py index 3d0e55655..3c11a4a6b 100644 --- a/coconut/command/command.py +++ b/coconut/command/command.py @@ -472,7 +472,7 @@ def handle_input(self, code): line = self.get_input(more=True) if line is None: return None - elif line.strip(): + elif line: code += "\n" + line else: break diff --git a/coconut/icoconut/root.py b/coconut/icoconut/root.py index 895aeee38..d7d2ff31d 100644 --- a/coconut/icoconut/root.py +++ b/coconut/icoconut/root.py @@ -47,11 +47,13 @@ parse_block_memo = {} -def memoized_parse_block(code): +def memoized_parse_block(code, none_if_not_found=False): """Memoized version of parse_block.""" - if code in parse_block_memo: + try: result = parse_block_memo[code] - else: + except KeyError: + if none_if_not_found: + return None try: parsed = COMPILER.parse_block(code) except Exception as err: @@ -98,15 +100,16 @@ def __init__(self, *args, **kwargs): def _coconut_compile(self, source, *args, **kwargs): """Version of _compile that compiles Coconut code first.""" + awaiting_input = "\n" in source.rstrip() and not source.endswith("\n\n") try: - compiled = memoized_parse_block(source) + compiled = memoized_parse_block(source, none_if_not_found=awaiting_input) except CoconutException as err: - if source.endswith("\n\n"): - raise err.syntax_err() - else: - return None + raise err.syntax_err() else: - return self._python_compile(compiled) + if compiled is None: + return None + else: + return self._python_compile(compiled) class CoconutShell(ZMQInteractiveShell, object): From 7cfb344163e37d375db4434935fe45b3d13e464e Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 18 Feb 2017 19:11:12 -0800 Subject: [PATCH 024/176] Bumps develop version --- coconut/root.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coconut/root.py b/coconut/root.py index 2ff9859e0..d51e443b5 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -26,7 +26,7 @@ VERSION = "1.2.1" VERSION_NAME = "Colonel" # False for release, int >= 1 for develop -DEVELOP = 2 +DEVELOP = 3 #----------------------------------------------------------------------------------------------------------------------- # CONSTANTS: From 0d1be5059e1fae645d32ccadbfe933fe6f8119e0 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 18 Feb 2017 19:42:17 -0800 Subject: [PATCH 025/176] Fix jupyter future statements Refs #222. --- coconut/icoconut/root.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/coconut/icoconut/root.py b/coconut/icoconut/root.py index d7d2ff31d..7e20509f5 100644 --- a/coconut/icoconut/root.py +++ b/coconut/icoconut/root.py @@ -67,6 +67,11 @@ def memoized_parse_block(code, none_if_not_found=False): return result +def memoized_parse_sys(code): + """Memoized version of parse_sys.""" + return COMPILER.header_proc(memoized_parse_block(code), header="sys", initial="none") + + #----------------------------------------------------------------------------------------------------------------------- # KERNEL: #----------------------------------------------------------------------------------------------------------------------- @@ -75,15 +80,10 @@ def memoized_parse_block(code, none_if_not_found=False): class CoconutCompiler(CachingCompiler, object): """IPython compiler for Coconut.""" - def __init__(self, *args, **kwargs): - """Version of __init__ that remembers header futures.""" - super(CoconutCompiler, self).__init__(*args, **kwargs) - super(CoconutCompiler, self).ast_parse(COMPILER.getheader("sys")) - def ast_parse(self, source, *args, **kwargs): """Version of ast_parse that compiles Coconut code first.""" try: - compiled = memoized_parse_block(source) + compiled = memoized_parse_sys(source) except CoconutException as err: raise err.syntax_err() else: @@ -129,7 +129,7 @@ def init_create_namespaces(self, *args, **kwargs): def run_cell(self, raw_cell, store_history=False, silent=False, shell_futures=None): """Version of run_cell that always uses shell_futures.""" - return super(CoconutShell, self).run_cell(raw_cell, store_history, silent) + return super(CoconutShell, self).run_cell(raw_cell, store_history, silent, shell_futures=True) def user_expressions(self, expressions): """Version of user_expressions that compiles Coconut code first.""" From 2ef8cac07e554038ce3930928e8f85df958a861e Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 18 Feb 2017 19:56:47 -0800 Subject: [PATCH 026/176] Prevents jupyter from invalidating valid input --- coconut/icoconut/root.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/coconut/icoconut/root.py b/coconut/icoconut/root.py index 7e20509f5..ff6d79584 100644 --- a/coconut/icoconut/root.py +++ b/coconut/icoconut/root.py @@ -35,6 +35,7 @@ code_exts, ) from coconut.compiler import Compiler +from coconut.compiler.util import should_indent from coconut.command.util import Runner #----------------------------------------------------------------------------------------------------------------------- @@ -100,11 +101,14 @@ def __init__(self, *args, **kwargs): def _coconut_compile(self, source, *args, **kwargs): """Version of _compile that compiles Coconut code first.""" - awaiting_input = "\n" in source.rstrip() and not source.endswith("\n\n") + awaiting_input = ("\n" in source.rstrip() or should_indent(source)) and not source.endswith("\n\n") try: compiled = memoized_parse_block(source, none_if_not_found=awaiting_input) except CoconutException as err: - raise err.syntax_err() + if source.endswith("\n\n"): + raise err.syntax_err() + else: + return None else: if compiled is None: return None From 2bda1647802faaa03d9836fcb5b85284153f71b8 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 18 Feb 2017 20:08:21 -0800 Subject: [PATCH 027/176] Bumps develop version, clarifies contributing docs --- CONTRIBUTING.md | 2 +- coconut/root.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d5a36d88c..4b6ebb25e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Coconut Contributing Guidelines -**Anyone is welcome to submit a pull request regardless of whether or not they have read this document.** The purpose of this document is simply to explain the contribution process and the internals of how Coconut works to make contributing easier. +**Anyone is welcome to submit an issue or pull request regardless of whether or not they have read this document.** The purpose of this document is simply to explain the contribution process and the internals of how Coconut works to make contributing easier. ## Asking Questions diff --git a/coconut/root.py b/coconut/root.py index d51e443b5..1dd7b44e5 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -26,7 +26,7 @@ VERSION = "1.2.1" VERSION_NAME = "Colonel" # False for release, int >= 1 for develop -DEVELOP = 3 +DEVELOP = 4 #----------------------------------------------------------------------------------------------------------------------- # CONSTANTS: From da59b8360a949d5a3e991bb29d0200ba10322e54 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sun, 19 Feb 2017 00:38:42 -0800 Subject: [PATCH 028/176] Starts pre-release process --- Makefile | 2 +- coconut/constants.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index c60ca7938..40ec9c06d 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ dev: pre-commit install -f --install-hooks .PHONY: format -format: +format: dev pre-commit autoupdate pre-commit run --allow-unstaged-config --all-files diff --git a/coconut/constants.py b/coconut/constants.py index f78766093..b9fbe4057 100644 --- a/coconut/constants.py +++ b/coconut/constants.py @@ -82,7 +82,7 @@ def fixpath(path): req_vers = { "pyparsing": (2, 1, 10), - "pre-commit": (0, 12), + "pre-commit": (0, 13), "sphinx": (1, 5), "pygments": (2, 2), "recommonmark": (0, 4), From eb1587abd8d0d80ea4be0d9456649e1bfb9fbe46 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sun, 19 Feb 2017 00:47:03 -0800 Subject: [PATCH 029/176] Sets version to v1.2.2 --- coconut/root.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/coconut/root.py b/coconut/root.py index 1dd7b44e5..0e809199a 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -23,10 +23,10 @@ # VERSION: #----------------------------------------------------------------------------------------------------------------------- -VERSION = "1.2.1" +VERSION = "1.2.2" VERSION_NAME = "Colonel" # False for release, int >= 1 for develop -DEVELOP = 4 +DEVELOP = False #----------------------------------------------------------------------------------------------------------------------- # CONSTANTS: From 794db824c7cf53eeb625bbdd9428f5636267ed64 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sun, 19 Feb 2017 23:24:03 -0800 Subject: [PATCH 030/176] Switches back to develop --- coconut/root.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coconut/root.py b/coconut/root.py index 0e809199a..5b3bbaec4 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -26,7 +26,7 @@ VERSION = "1.2.2" VERSION_NAME = "Colonel" # False for release, int >= 1 for develop -DEVELOP = False +DEVELOP = 1 #----------------------------------------------------------------------------------------------------------------------- # CONSTANTS: From ae3ceca82a2df318b3f228da4cdceb232e15d8ff Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Mon, 20 Feb 2017 01:32:09 -0800 Subject: [PATCH 031/176] Fixes digit handling --- coconut/compiler/grammar.py | 7 +++++-- coconut/highlighter.py | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/coconut/compiler/grammar.py b/coconut/compiler/grammar.py index e01f12914..cbdf7d419 100644 --- a/coconut/compiler/grammar.py +++ b/coconut/compiler/grammar.py @@ -523,9 +523,12 @@ class Grammar(object): hexint = Combine(Word(hexnums) + ZeroOrMore(underscore.suppress() + Word(hexnums))) imag_j = CaselessLiteral("j") | fixto(CaselessLiteral("i"), "j") - basenum = Combine(integer + dot + (integer | FollowedBy(imag_j) | ~name) | Optional(integer) + dot + integer) | integer + basenum = Combine( + integer + dot + (integer | FollowedBy(imag_j) | ~name) + | Optional(integer) + dot + integer + ) | integer sci_e = Combine(CaselessLiteral("e") + Optional(plus | neg_minus)) - numitem = Combine(basenum + Optional(sci_e + integer)) + numitem = ~(Literal("0") + Word(nums + "_", exact=1)) + Combine(basenum + Optional(sci_e + integer)) imag_num = Combine(numitem + imag_j) bin_num = Combine(CaselessLiteral("0b") + Optional(underscore.suppress()) + binint) oct_num = Combine(CaselessLiteral("0o") + Optional(underscore.suppress()) + octint) diff --git a/coconut/highlighter.py b/coconut/highlighter.py index 810cd7081..a07be4a81 100644 --- a/coconut/highlighter.py +++ b/coconut/highlighter.py @@ -91,7 +91,7 @@ class CoconutLexer(Python3Lexer): (r"0b[01_]+", Number.Integer), (r"0o[0-7_]+", Number.Integer), (r"0x[\da-fA-F_]+", Number.Integer), - (r"\d[\d_]*(\.\d[\d_]*)?", Number.Integer), + (r"\d[\d_]*(\.\d[\d_]*)?((e|E)[\d_]+)?(j|J)?", Number.Integer), ] + tokens["numbers"] def __init__(self, stripnl=False, stripall=False, ensurenl=True, tabsize=tabideal, encoding=default_encoding): From 6f7bd80374a3da4950ddc5e83f399a0b18f9d86e Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Mon, 20 Feb 2017 01:32:51 -0800 Subject: [PATCH 032/176] Cuts out more compilation from Jupyter kernel Resolves #221. --- coconut/icoconut/root.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/coconut/icoconut/root.py b/coconut/icoconut/root.py index ff6d79584..5e7b8d002 100644 --- a/coconut/icoconut/root.py +++ b/coconut/icoconut/root.py @@ -48,13 +48,11 @@ parse_block_memo = {} -def memoized_parse_block(code, none_if_not_found=False): +def memoized_parse_block(code): """Memoized version of parse_block.""" try: result = parse_block_memo[code] except KeyError: - if none_if_not_found: - return None try: parsed = COMPILER.parse_block(code) except Exception as err: @@ -97,23 +95,25 @@ class CoconutSplitter(IPythonInputSplitter, object): def __init__(self, *args, **kwargs): """Version of __init__ that sets up Coconut code compilation.""" super(CoconutSplitter, self).__init__(*args, **kwargs) - self._python_compile, self._compile = self._compile, self._coconut_compile + self._compile = self._coconut_compile def _coconut_compile(self, source, *args, **kwargs): - """Version of _compile that compiles Coconut code first.""" - awaiting_input = ("\n" in source.rstrip() or should_indent(source)) and not source.endswith("\n\n") - try: - compiled = memoized_parse_block(source, none_if_not_found=awaiting_input) - except CoconutException as err: - if source.endswith("\n\n"): - raise err.syntax_err() - else: - return None - else: - if compiled is None: + """Version of _compile that compiles Coconut code. + None means that the code should not be run as is. + Any other value means that it can.""" + if source.endswith("\n\n"): + return True + elif should_indent(source): + return None + elif "\n" not in source.rstrip(): # if at start + try: + memoized_parse_block(source) + except CoconutException: return None else: - return self._python_compile(compiled) + return True + else: + return True class CoconutShell(ZMQInteractiveShell, object): From 6768256cceb7dc56570d080d641d54abf6155b4c Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Mon, 20 Feb 2017 01:37:15 -0800 Subject: [PATCH 033/176] Bumps develop version --- coconut/root.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coconut/root.py b/coconut/root.py index 5b3bbaec4..1fa626d01 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -26,7 +26,7 @@ VERSION = "1.2.2" VERSION_NAME = "Colonel" # False for release, int >= 1 for develop -DEVELOP = 1 +DEVELOP = 2 #----------------------------------------------------------------------------------------------------------------------- # CONSTANTS: From dcb52cbae6b6ecaea55ebb556628ebcb5bdefde6 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 21 Feb 2017 23:01:39 -0800 Subject: [PATCH 034/176] Does not exit on error when --watch is passed Refs #224. --- coconut/command/command.py | 7 ++++--- coconut/root.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/coconut/command/command.py b/coconut/command/command.py index 3c11a4a6b..212ea013b 100644 --- a/coconut/command/command.py +++ b/coconut/command/command.py @@ -199,7 +199,7 @@ def use_args(self, args, interact=True): else: package = None # auto-decide package - with self.running_jobs(): + with self.running_jobs(exit_on_error=not args.watch): filepaths = self.compile_path(args.source, dest, package, args.run or args.interact, args.force) self.run_mypy(filepaths) @@ -395,7 +395,7 @@ def set_jobs(self, jobs): self.jobs = jobs @contextmanager - def running_jobs(self): + def running_jobs(self, exit_on_error=True): """Initialize multiprocessing.""" with self.handling_exceptions(): if self.jobs == 0: @@ -407,7 +407,8 @@ def running_jobs(self): yield finally: self.executor = None - self.exit_on_error() + if exit_on_error: + self.exit_on_error() def create_package(self, dirpath): """Sets up a package directory.""" diff --git a/coconut/root.py b/coconut/root.py index 1fa626d01..0a8d1e4fc 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -26,7 +26,7 @@ VERSION = "1.2.2" VERSION_NAME = "Colonel" # False for release, int >= 1 for develop -DEVELOP = 2 +DEVELOP = 3 #----------------------------------------------------------------------------------------------------------------------- # CONSTANTS: From 6c219432189d5181f563fa60b1e6d96959182bc9 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 21 Feb 2017 23:13:57 -0800 Subject: [PATCH 035/176] Adds info on contributor friendly issues --- CONTRIBUTING.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4b6ebb25e..4eb10b6d4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,6 +14,10 @@ Contributing to Coconut is as simple as 2. making changes to the [`develop` branch](https://github.com/evhub/coconut/tree/develop), and 3. proposing a pull request. +## Contributor Friendly Issues + +Want to help out, but don't know what to work on? Head over to Coconut's [open issues](https://github.com/evhub/coconut/issues) and look for ones labeled "contributor friendly." Contributor friendly issues are those that require less intimate knowledge of Coconut's inner workings, and are thus possible for new contributors to work on. + ## File Layout - `DOCS.md` From cda9d70f71764524c76a2120d582a6b9f9249e5c Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Wed, 22 Feb 2017 00:28:58 -0800 Subject: [PATCH 036/176] Cleans up command utilities --- coconut/command/util.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/coconut/command/util.py b/coconut/command/util.py index 3feea993e..4b152c334 100644 --- a/coconut/command/util.py +++ b/coconut/command/util.py @@ -226,11 +226,14 @@ def call_output(cmd): def run_cmd(cmd, show_output=True, raise_errs=True): """Runs a console command.""" - if not isinstance(cmd, list): - raise CoconutInternalException("console commands must be passed as lists") + if not cmd or not isinstance(cmd, list): + raise CoconutInternalException("console commands must be passed as non-empty lists") else: - if sys.version_info >= (3, 3): + try: import shutil + except ImportError: + pass + else: cmd[0] = shutil.which(cmd[0]) or cmd[0] logger.log_cmd(cmd) if show_output and raise_errs: @@ -266,11 +269,11 @@ class Prompt(object): def __init__(self): """Set up the prompt.""" - if style_env_var in os.environ: - self.set_style(os.environ[style_env_var]) - elif prompt_toolkit is not None: - self.style = default_style if prompt_toolkit is not None: + if style_env_var in os.environ: + self.set_style(os.environ[style_env_var]) + else: + self.style = default_style self.history = prompt_toolkit.history.InMemoryHistory() def set_style(self, style): From 0e477eb4f672bae59b76362f842b056816148e92 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Wed, 22 Feb 2017 00:55:00 -0800 Subject: [PATCH 037/176] Improves documentation, tests --- DOCS.md | 4 ++-- FAQ.md | 2 +- coconut/stubs/__coconut__.pyi | 11 +++++------ tests/main_test.py | 4 ++++ 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/DOCS.md b/DOCS.md index 8f73267ac..893e493e1 100644 --- a/DOCS.md +++ b/DOCS.md @@ -271,7 +271,7 @@ In addition to function argument type annotation, Coconut also supports variable Coconut even supports `--mypy` in the interpreter, which will intelligently scan each new line of code, in the context of previous lines, for newly-introduced MyPy errors. For example: ```coconut ->>> a = count()[0] # type: str +>>> a: str = count()[0] :14: error: Incompatible types in assignment (expression has type "int", variable has type "str") ``` @@ -1583,7 +1583,7 @@ Coconut provides a `recursive_iterator` decorator that provides significant opti 1. your function either always `return`s an iterator or generates an iterator using `yield`, 2. when called multiple times with the same arguments, your function produces the same iterator (your function is stateless), -3. your function calls itself multiple times with the same arguments, and +3. your function gets called multiple times with the same arguments, and 4. all arguments passed to your function have a unique pickling (this should almost always be true). If you are encountering a `RuntimeError` due to maximum recursion depth, it is highly recommended that you rewrite your function to meet either the criteria above for `recursive_iterator`, or the corresponding criteria for Coconut's [tail call optimization](#tail-call-optimization), either of which should prevent such errors. diff --git a/FAQ.md b/FAQ.md index 29a554356..3b87b6133 100644 --- a/FAQ.md +++ b/FAQ.md @@ -33,7 +33,7 @@ Information on every Coconut release is chronicled on the [GitHub releases page] ### Help! I tried to write a recursive iterator and my Python segfaulted! -No problem—just use Coconut's [`recursive_iterator`](http://coconut.readthedocs.io/en/master/DOCS.html#recursive_iterator) decorator and you should be fine. This is a [known Python issue](http://bugs.python.org/issue14010) but `recursive_iterator` will fix it for you. +No problem—just use Coconut's [`recursive_iterator`](http://coconut.readthedocs.io/en/master/DOCS.html#recursive-iterator) decorator and you should be fine. This is a [known Python issue](http://bugs.python.org/issue14010) but `recursive_iterator` will fix it for you. ### If I'm already perfectly happy with Python, why should I learn Coconut? diff --git a/coconut/stubs/__coconut__.pyi b/coconut/stubs/__coconut__.pyi index 9bde608d5..ba007c79c 100644 --- a/coconut/stubs/__coconut__.pyi +++ b/coconut/stubs/__coconut__.pyi @@ -18,17 +18,13 @@ _T = TypeVar('_T') _S = TypeVar('_S') -if sys.version_info >= (3,): - import builtins as _b - ascii, filter, hex, map, oct, zip, open, chr, str, range = _b.ascii, _b.filter, _b.hex, _b.map, _b.oct, _b.zip, _b.open, _b.chr, _b.str, _b.range - -else: +if sys.version_info < (3,): import __builtin__ as _b from future_builtins import * from io import open py_raw_input, py_xrange = _b.raw_input, _b.xrange - chr, str = _b.unichr, _b.unicode + # chr, str = _b.unichr, _b.unicode class range: def __init__(self, @@ -44,6 +40,9 @@ else: def __hash__(self) -> int: ... def count(self, elem: int) -> int: ... def index(self, elem: int) -> int: ... +else: + import builtins as _b + ascii, filter, hex, map, oct, zip, open, chr, str, range = _b.ascii, _b.filter, _b.hex, _b.map, _b.oct, _b.zip, _b.open, _b.chr, _b.str, _b.range py_chr, py_filter, py_hex, py_input, py_int, py_map, py_oct, py_open, py_print, py_range, py_str, py_zip, py_filter, py_reversed, py_enumerate = _b.chr, _b.filter, _b.hex, _b.input, _b.int, _b.map, _b.oct, _b.open, _b.print, _b.range, _b.str, _b.zip, _b.filter, _b.reversed, _b.enumerate diff --git a/tests/main_test.py b/tests/main_test.py index 14b459f4e..de2d4420f 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -44,6 +44,9 @@ coconut_snip = r"msg = ''; pmsg = print$(msg); `pmsg`" +mypy_snip = "a: str = count()[0]" +mypy_snip_err = 'error: Incompatible types in assignment (expression has type "int", variable has type "str")' + ignore_mypy_errs_with = ( "already defined", "Cannot determine type of", @@ -281,6 +284,7 @@ def test_jobs_zero(self): if sys.version_info >= (3, 4): def test_mypy(self): + call(["coconut", "-c", mypy_snip], assert_output=mypy_snip_err, check_mypy=False) run(["--mypy", "--ignore-missing-imports"]) def test_strict(self): From 5f55870cebc3876df8d303f0167ac6a8bde74e61 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Wed, 22 Feb 2017 11:54:22 -0800 Subject: [PATCH 038/176] Fixes a broken test --- tests/main_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/main_test.py b/tests/main_test.py index de2d4420f..eb1b56f01 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -44,7 +44,7 @@ coconut_snip = r"msg = ''; pmsg = print$(msg); `pmsg`" -mypy_snip = "a: str = count()[0]" +mypy_snip = r"a: str = count()[0]" mypy_snip_err = 'error: Incompatible types in assignment (expression has type "int", variable has type "str")' ignore_mypy_errs_with = ( @@ -284,7 +284,7 @@ def test_jobs_zero(self): if sys.version_info >= (3, 4): def test_mypy(self): - call(["coconut", "-c", mypy_snip], assert_output=mypy_snip_err, check_mypy=False) + call(["coconut", "-c", mypy_snip, "--mypy"], assert_output=mypy_snip_err, check_mypy=False) run(["--mypy", "--ignore-missing-imports"]) def test_strict(self): From c59092ed74080c3865f4c4f6408131a6ce3a8f35 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sun, 26 Feb 2017 00:41:07 -0800 Subject: [PATCH 039/176] Adds guess_lexer support --- coconut/constants.py | 7 +++++++ coconut/highlighter.py | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/coconut/constants.py b/coconut/constants.py index b9fbe4057..c334fc57f 100644 --- a/coconut/constants.py +++ b/coconut/constants.py @@ -400,6 +400,8 @@ def fixpath(path): # HIGHLIGHTER CONSTANTS: #----------------------------------------------------------------------------------------------------------------------- +shebang_regex = r'coconut(-run)?' + builtins = ( "reduce", "takewhile", @@ -436,6 +438,11 @@ def fixpath(path): r"`", r"::", r"(?!\.\.\.)\.\.", + r"\|>", + r"<\|", + r"\|\*>", + r"<\*\|", + r"->", r"\u2192", r"\u21a6", r"\u21a4", diff --git a/coconut/highlighter.py b/coconut/highlighter.py index a07be4a81..d3c0e681e 100644 --- a/coconut/highlighter.py +++ b/coconut/highlighter.py @@ -22,6 +22,7 @@ from pygments.lexers import Python3Lexer, PythonConsoleLexer from pygments.token import Text, Operator, Keyword, Name, Number from pygments.lexer import words, bygroups +from pygments.util import shebang_matches from coconut.constants import ( builtins, @@ -30,6 +31,7 @@ default_encoding, code_exts, reserved_vars, + shebang_regex, ) #----------------------------------------------------------------------------------------------------------------------- @@ -98,3 +100,6 @@ def __init__(self, stripnl=False, stripall=False, ensurenl=True, tabsize=tabidea """Initialize the Python syntax highlighter.""" Python3Lexer.__init__(self, stripnl=stripnl, stripall=stripall, ensurenl=ensurenl, tabsize=tabsize, encoding=default_encoding) self.original_add_filter, self.add_filter = self.add_filter, lenient_add_filter + + def analyse_text(text): + return shebang_matches(text, shebang_regex) From 0c3440a6264a8384f0b46a0f76abb6e13887dba9 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Mon, 27 Feb 2017 18:30:50 -0800 Subject: [PATCH 040/176] Adds alternative class notation support Refs #226. --- coconut/compiler/compiler.py | 11 +++++++++-- coconut/compiler/grammar.py | 9 +++++---- tests/src/cocotest/agnostic/suite.coco | 3 +++ tests/src/cocotest/agnostic/util.coco | 9 +++++++++ 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/coconut/compiler/compiler.py b/coconut/compiler/compiler.py index 44d01768f..69310eef8 100644 --- a/coconut/compiler/compiler.py +++ b/coconut/compiler/compiler.py @@ -1292,7 +1292,8 @@ def tre_return_handle(original, loc, tokens): tre_return_handle) def decoratable_normal_funcdef_stmt_handle(self, tokens): - """Determines if tail call optimization can be done and if so does it.""" + """Determines if TCO or TRE can be done and if so does it. + Also handles dotted function names.""" if len(tokens) == 1: decorators, funcdef = None, tokens[0] elif len(tokens) == 2: @@ -1305,6 +1306,8 @@ def decoratable_normal_funcdef_stmt_handle(self, tokens): tre = False # wether tre was done level = 0 # indentation level disabled_until_level = None # whether inside of a def/try/with + attempt_tre = False # whether to attempt tre at all + undotted_name = None # the function __name__ if func_name is a dotted name raw_lines = funcdef.splitlines(True) def_stmt, raw_lines = raw_lines[0], raw_lines[1:] @@ -1312,8 +1315,10 @@ def decoratable_normal_funcdef_stmt_handle(self, tokens): func_name, func_args, func_params = parse(self.split_func_name_args_params, def_stmt) except ParseBaseException as err: complain(self.make_parse_err(err, reformat=False, include_ln=False)) - attempt_tre = False else: + if "." in func_name: + undotted_name = func_name.rsplit(".", 1)[-1] + def_stmt = def_stmt.replace(func_name, undotted_name) use_mock = func_args and func_args != func_params[1:-1] func_store = tre_store_var + "_" + str(self.tre_store_count) self.tre_store_count += 1 @@ -1372,6 +1377,8 @@ def decoratable_normal_funcdef_stmt_handle(self, tokens): out = "@_coconut_tco\n" + out if decorators: out = decorators + out + if undotted_name is not None: + out += func_name + " = " + undotted_name + "\n" return out def split_args_list(self, original, loc, tokens): diff --git a/coconut/compiler/grammar.py b/coconut/compiler/grammar.py index cbdf7d419..22c77e37d 100644 --- a/coconut/compiler/grammar.py +++ b/coconut/compiler/grammar.py @@ -516,6 +516,7 @@ class Grammar(object): for k in reserved_vars: base_name |= backslash.suppress() + Keyword(k) dotted_name = condense(name + ZeroOrMore(dot + name)) + dotted_base_name = condense(base_name + ZeroOrMore(dot + base_name)) integer = Combine(Word(nums) + ZeroOrMore(underscore.suppress() + Word(nums))) binint = Combine(Word("01") + ZeroOrMore(underscore.suppress() + Word("01"))) @@ -1060,10 +1061,10 @@ class Grammar(object): ) + rparen.suppress() return_typedef = Forward() - name_funcdef = trace(condense(name + parameters)) + name_funcdef = trace(condense(dotted_name + parameters)) op_tfpdef = unsafe_typedef_default | condense(name + Optional(default)) op_funcdef_arg = name | condense(lparen.suppress() + op_tfpdef + rparen.suppress()) - op_funcdef_name = backtick.suppress() + name + backtick.suppress() + op_funcdef_name = backtick.suppress() + dotted_name + backtick.suppress() op_funcdef = trace(attach( Group(Optional(op_funcdef_arg)) + op_funcdef_name @@ -1077,7 +1078,7 @@ class Grammar(object): name_match_funcdef = Forward() op_match_funcdef = Forward() - name_match_funcdef_ref = name + lparen.suppress() + match_args_list + match_guard + rparen.suppress() + name_match_funcdef_ref = dotted_name + lparen.suppress() + match_args_list + match_guard + rparen.suppress() op_match_funcdef_arg = Group(Optional(lparen.suppress() + Group(match + Optional(equals.suppress() + test)) + rparen.suppress())) @@ -1224,7 +1225,7 @@ class Grammar(object): comma + Optional(passthrough)))) split_func_name_args_params = attach( - (start_marker + Keyword("def")).suppress() + base_name + lparen.suppress() + (start_marker + Keyword("def")).suppress() + dotted_base_name + lparen.suppress() + parameters_tokens + rparen.suppress(), split_func_name_args_params_handle) diff --git a/tests/src/cocotest/agnostic/suite.coco b/tests/src/cocotest/agnostic/suite.coco index 6102421e7..a2d766367 100644 --- a/tests/src/cocotest/agnostic/suite.coco +++ b/tests/src/cocotest/agnostic/suite.coco @@ -272,4 +272,7 @@ def suite_test(): pass else: assert False + a = altclass() + assert a.func(1) == 1 + assert a.zero(10) == 0 return True diff --git a/tests/src/cocotest/agnostic/util.coco b/tests/src/cocotest/agnostic/util.coco index 301407764..3f2ebca7c 100644 --- a/tests/src/cocotest/agnostic/util.coco +++ b/tests/src/cocotest/agnostic/util.coco @@ -599,3 +599,12 @@ def x_is_int_def_0(x is int = 0) = x def head_tail_def_none([head] + tail = [None]) = (head, tail) def kwd_only_x_is_int_def_0(*, x is int = 0) = x + +# Alternative Class Notation + +class altclass +def altclass.func(self, x) = x +def altclass.zero(self, x) = + if x == 0: + return 0 + altclass.zero(self, x-1) From 5c540bb309c8190163eaeccb6566ef6cf1cd8d3d Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Mon, 27 Feb 2017 18:50:08 -0800 Subject: [PATCH 041/176] Fixes dotted function TCO --- coconut/compiler/compiler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coconut/compiler/compiler.py b/coconut/compiler/compiler.py index 69310eef8..2e96f4d35 100644 --- a/coconut/compiler/compiler.py +++ b/coconut/compiler/compiler.py @@ -1370,7 +1370,7 @@ def decoratable_normal_funcdef_stmt_handle(self, tokens): + openindent + base + base_dedent + ("\n" if "\n" not in base_dedent else "") + "return None" + ("\n" if "\n" not in dedent else "") + closeindent + dedent - + func_store + " = " + func_name + "\n" + + func_store + " = " + (func_name if undotted_name is None else undotted_name) + "\n" ) out = def_stmt + out if tco: From 97abcf0233e0e72d43e8b70c14b7c1ea4c6f81db Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Wed, 1 Mar 2017 00:38:22 -0800 Subject: [PATCH 042/176] Fixes yield issues --- coconut/compiler/compiler.py | 3 ++- coconut/compiler/grammar.py | 2 +- tests/src/cocotest/agnostic/suite.coco | 2 ++ tests/src/cocotest/agnostic/util.coco | 33 ++++++++++++++++++++++++++ 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/coconut/compiler/compiler.py b/coconut/compiler/compiler.py index 2e96f4d35..dff2ebc62 100644 --- a/coconut/compiler/compiler.py +++ b/coconut/compiler/compiler.py @@ -1333,7 +1333,8 @@ def decoratable_normal_funcdef_stmt_handle(self, tokens): if disabled_until_level is None: if match_in(Keyword("yield"), body): # we can't tco generators - return tokens[0] + lines = raw_lines + break elif match_in(Keyword("def") | Keyword("try") | Keyword("with"), body): disabled_until_level = level else: diff --git a/coconut/compiler/grammar.py b/coconut/compiler/grammar.py index 22c77e37d..5f0b2ea3f 100644 --- a/coconut/compiler/grammar.py +++ b/coconut/compiler/grammar.py @@ -621,7 +621,7 @@ class Grammar(object): yield_from = Forward() dict_comp = Forward() - yield_classic = addspace(Keyword("yield") + testlist) + yield_classic = addspace(Keyword("yield") + Optional(testlist)) yield_from_ref = Keyword("yield").suppress() + Keyword("from").suppress() + test yield_expr = yield_from | yield_classic dict_comp_ref = lbrace.suppress() + (test + colon.suppress() + test | dubstar_expr) + comp_for + rbrace.suppress() diff --git a/tests/src/cocotest/agnostic/suite.coco b/tests/src/cocotest/agnostic/suite.coco index a2d766367..7f597b5f7 100644 --- a/tests/src/cocotest/agnostic/suite.coco +++ b/tests/src/cocotest/agnostic/suite.coco @@ -275,4 +275,6 @@ def suite_test(): a = altclass() assert a.func(1) == 1 assert a.zero(10) == 0 + with Vars.using(): + assert var_one == 1 return True diff --git a/tests/src/cocotest/agnostic/util.coco b/tests/src/cocotest/agnostic/util.coco index 3f2ebca7c..f28c8ce06 100644 --- a/tests/src/cocotest/agnostic/util.coco +++ b/tests/src/cocotest/agnostic/util.coco @@ -608,3 +608,36 @@ def altclass.zero(self, x) = if x == 0: return 0 altclass.zero(self, x-1) + +# Logic Stuff + +class Vars: + var_one = 1 + + @classmethod + def items(cls): + for name, var in vars(cls).items(): + if not name.startswith("_"): + yield name, var + @classmethod + def use(cls): + """Put variables into the global namespace.""" + for name, var in cls.items(): + globals()[name] = var + @classmethod + @contextmanager + def using(cls): + """Temporarilty put variables into the global namespace.""" + prevars = {} + for name, var in cls.items(): + if name in globals(): + prevars[name] = globals()[name] + globals()[name] = var + try: + yield + finally: + for name, var in cls.items(): + if name in prevars: + globals()[name] = prevars[name] + else: + del globals()[name] From f65b16bb5e2f6675f04c1bee1abe9839b2498ffb Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Wed, 1 Mar 2017 13:12:53 -0800 Subject: [PATCH 043/176] Fixes an invalid test --- tests/src/cocotest/agnostic/suite.coco | 6 ++++++ tests/src/cocotest/agnostic/util.coco | 1 + 2 files changed, 7 insertions(+) diff --git a/tests/src/cocotest/agnostic/suite.coco b/tests/src/cocotest/agnostic/suite.coco index 7f597b5f7..dfb57b837 100644 --- a/tests/src/cocotest/agnostic/suite.coco +++ b/tests/src/cocotest/agnostic/suite.coco @@ -277,4 +277,10 @@ def suite_test(): assert a.zero(10) == 0 with Vars.using(): assert var_one == 1 + try: + var_one + except NameError: + assert True + else: + assert False return True diff --git a/tests/src/cocotest/agnostic/util.coco b/tests/src/cocotest/agnostic/util.coco index f28c8ce06..3f0af721c 100644 --- a/tests/src/cocotest/agnostic/util.coco +++ b/tests/src/cocotest/agnostic/util.coco @@ -1,5 +1,6 @@ # Imports: import random +from contextlib import contextmanager # Random Number Helper: def rand_list(n): From 1a850a1ac5107bdda92842fb99bb5aedcd781b4a Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Thu, 2 Mar 2017 23:54:51 -0800 Subject: [PATCH 044/176] Makes --interact suppress exiting on error --- coconut/command/command.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coconut/command/command.py b/coconut/command/command.py index 212ea013b..d59d766e6 100644 --- a/coconut/command/command.py +++ b/coconut/command/command.py @@ -199,7 +199,7 @@ def use_args(self, args, interact=True): else: package = None # auto-decide package - with self.running_jobs(exit_on_error=not args.watch): + with self.running_jobs(exit_on_error=not (args.watch or args.interact)): filepaths = self.compile_path(args.source, dest, package, args.run or args.interact, args.force) self.run_mypy(filepaths) From 339072efa91fc47e038c2a7d86326c759d05ef6f Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Thu, 2 Mar 2017 23:57:29 -0800 Subject: [PATCH 045/176] Revert "Makes --interact suppress exiting on error" This reverts commit 1a850a1ac5107bdda92842fb99bb5aedcd781b4a. --- coconut/command/command.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coconut/command/command.py b/coconut/command/command.py index d59d766e6..212ea013b 100644 --- a/coconut/command/command.py +++ b/coconut/command/command.py @@ -199,7 +199,7 @@ def use_args(self, args, interact=True): else: package = None # auto-decide package - with self.running_jobs(exit_on_error=not (args.watch or args.interact)): + with self.running_jobs(exit_on_error=not args.watch): filepaths = self.compile_path(args.source, dest, package, args.run or args.interact, args.force) self.run_mypy(filepaths) From e5eea287ab63be3f658bb62ffd1f6ffaefd4e56f Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Fri, 3 Mar 2017 18:33:59 -0800 Subject: [PATCH 046/176] Fixes tests, command running --- coconut/command/util.py | 4 ++-- tests/src/cocotest/agnostic/suite.coco | 2 +- tests/src/cocotest/agnostic/util.coco | 20 ++++++++++++-------- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/coconut/command/util.py b/coconut/command/util.py index 4b152c334..efdaa47f0 100644 --- a/coconut/command/util.py +++ b/coconut/command/util.py @@ -230,11 +230,11 @@ def run_cmd(cmd, show_output=True, raise_errs=True): raise CoconutInternalException("console commands must be passed as non-empty lists") else: try: - import shutil + from shutil import which except ImportError: pass else: - cmd[0] = shutil.which(cmd[0]) or cmd[0] + cmd[0] = which(cmd[0]) or cmd[0] logger.log_cmd(cmd) if show_output and raise_errs: return subprocess.check_call(cmd) diff --git a/tests/src/cocotest/agnostic/suite.coco b/tests/src/cocotest/agnostic/suite.coco index dfb57b837..f19a0dfd8 100644 --- a/tests/src/cocotest/agnostic/suite.coco +++ b/tests/src/cocotest/agnostic/suite.coco @@ -275,7 +275,7 @@ def suite_test(): a = altclass() assert a.func(1) == 1 assert a.zero(10) == 0 - with Vars.using(): + with Vars.using(globals()): assert var_one == 1 try: var_one diff --git a/tests/src/cocotest/agnostic/util.coco b/tests/src/cocotest/agnostic/util.coco index 3f0af721c..af6936416 100644 --- a/tests/src/cocotest/agnostic/util.coco +++ b/tests/src/cocotest/agnostic/util.coco @@ -621,24 +621,28 @@ class Vars: if not name.startswith("_"): yield name, var @classmethod - def use(cls): + def use(cls, globs=None): """Put variables into the global namespace.""" + if globs is None: + globs = globals() for name, var in cls.items(): - globals()[name] = var + globs[name] = var @classmethod @contextmanager - def using(cls): + def using(cls, globs=None): """Temporarilty put variables into the global namespace.""" + if globs is None: + globs = globals() prevars = {} for name, var in cls.items(): - if name in globals(): - prevars[name] = globals()[name] - globals()[name] = var + if name in globs: + prevars[name] = globs[name] + globs[name] = var try: yield finally: for name, var in cls.items(): if name in prevars: - globals()[name] = prevars[name] + globs[name] = prevars[name] else: - del globals()[name] + del globs[name] From 6f3f242381697ccc7b22b4f04ab42b7bb07000a7 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 4 Mar 2017 14:08:23 -0800 Subject: [PATCH 047/176] Bumps develop version --- coconut/root.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coconut/root.py b/coconut/root.py index 0a8d1e4fc..2368ac881 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -26,7 +26,7 @@ VERSION = "1.2.2" VERSION_NAME = "Colonel" # False for release, int >= 1 for develop -DEVELOP = 3 +DEVELOP = 4 #----------------------------------------------------------------------------------------------------------------------- # CONSTANTS: From a6fd36eae7be5f18d71986d83c9e0b53f9fa2702 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 4 Mar 2017 18:51:14 -0800 Subject: [PATCH 048/176] Adds document_names --- setup.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/setup.py b/setup.py index 938436972..5fc53c08b 100644 --- a/setup.py +++ b/setup.py @@ -77,6 +77,10 @@ "coconut_pycon = coconut.highlighter:CoconutPythonConsoleLexer", ] }, + document_names={ + "description": "README.rst", + "license": "LICENSE.txt", + }, classifiers=classifiers, keywords=search_terms, ) From 986fc569531ab78ffe13a22f41ee01b1709aa59c Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 4 Mar 2017 18:58:56 -0800 Subject: [PATCH 049/176] Adds pyprover test --- tests/main_test.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/tests/main_test.py b/tests/main_test.py index eb1b56f01..e97adea21 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -37,10 +37,11 @@ src = os.path.join(base, "src") dest = os.path.join(base, "dest") -prisoner = os.path.join(os.curdir, "prisoner") pyston = os.path.join(os.curdir, "pyston") runnable_coco = os.path.join(src, "runnable.coco") runnable_py = os.path.join(src, "runnable.py") +prisoner = os.path.join(os.curdir, "prisoner") +pyprover = os.path.join(os.curdir, "pyprover") coconut_snip = r"msg = ''; pmsg = print$(msg); `pmsg`" @@ -225,6 +226,17 @@ def run_pyston(): call(["python", os.path.join(os.curdir, "pyston", "runner.py")], assert_output=True) +def comp_pyprover(args=[]): + """Compiles evhub/pyprover.""" + call(["git", "clone", "https://github.com/evhub/pyprover.git"]) + call_coconut(["pyprover", "--strict"] + args) + + +def run_pyprover(args=[]): + """Runs pyprover.""" + call(["python", os.path.join(os.curdir, "pyprover", "tests.py")], assert_output=True) + + def comp_all(args=[]): """Compile Coconut tests.""" try: @@ -315,6 +327,11 @@ def test_prisoner(self): with remove_when_done(prisoner): comp_prisoner() + def test_pyprover(self): + with remove_when_done(pyprover): + comp_pyrover() + run_pyprover() + def test_pyston(self): with remove_when_done(pyston): comp_pyston() From 04a14565c8dcc35eb211e2583328697b73b2fc29 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 4 Mar 2017 19:08:16 -0800 Subject: [PATCH 050/176] Uses new pyprover directory structure --- tests/main_test.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/main_test.py b/tests/main_test.py index e97adea21..da34bdbc4 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -37,12 +37,16 @@ src = os.path.join(base, "src") dest = os.path.join(base, "dest") -pyston = os.path.join(os.curdir, "pyston") runnable_coco = os.path.join(src, "runnable.coco") runnable_py = os.path.join(src, "runnable.py") prisoner = os.path.join(os.curdir, "prisoner") +pyston = os.path.join(os.curdir, "pyston") pyprover = os.path.join(os.curdir, "pyprover") +prisoner_git = "https://github.com/evhub/prisoner.git" +pyston_git = "https://github.com/evhub/pyston.git" +pyprover_git = "https://github.com/evhub/pyprover.git" + coconut_snip = r"msg = ''; pmsg = print$(msg); `pmsg`" mypy_snip = r"a: str = count()[0]" @@ -211,13 +215,13 @@ def run(args=[], agnostic_target=None, use_run_arg=False): def comp_prisoner(args=[]): """Compiles evhub/prisoner.""" - call(["git", "clone", "https://github.com/evhub/prisoner.git"]) + call(["git", "clone", prisoner_git]) call_coconut(["prisoner", "--strict"] + args) def comp_pyston(args=[]): """Compiles evhub/pyston.""" - call(["git", "clone", "https://github.com/evhub/pyston.git"]) + call(["git", "clone", pyston_git]) call_coconut(["pyston"] + args) @@ -228,8 +232,8 @@ def run_pyston(): def comp_pyprover(args=[]): """Compiles evhub/pyprover.""" - call(["git", "clone", "https://github.com/evhub/pyprover.git"]) - call_coconut(["pyprover", "--strict"] + args) + call(["git", "clone", pyprover_git]) + call_coconut(["pyprover-source", "pyprover", "--strict"] + args) def run_pyprover(args=[]): From 231a856e48474fea23c028d522e6d56bbfa44886 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 4 Mar 2017 19:19:09 -0800 Subject: [PATCH 051/176] Updates pre-commit --- .pre-commit-config.yaml | 4 ++-- coconut/command/mypy.py | 2 +- coconut/command/util.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 42d02df92..062e9ef1e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ - repo: git://github.com/pre-commit/pre-commit-hooks.git - sha: e626cd57090d8df0be21e4df0f4e55cc3511d6ab + sha: a11d9314b22d8f8c7556443875b731ef05965464 hooks: - id: check-added-large-files - id: check-byte-order-marker @@ -11,7 +11,7 @@ - --in-place - --aggressive - --aggressive - - --ignore=W503,E501 + - --ignore=W503,E501,E722 - id: flake8 args: - --ignore=W503,E501,E265,E402,F405,E305 diff --git a/coconut/command/mypy.py b/coconut/command/mypy.py index e66d31530..3d3d33f50 100644 --- a/coconut/command/mypy.py +++ b/coconut/command/mypy.py @@ -49,7 +49,7 @@ def mypy_run(args): main(None) except SystemExit: pass - except: + except BaseException: traceback.print_exc() out = [] diff --git a/coconut/command/util.py b/coconut/command/util.py index efdaa47f0..057128ebe 100644 --- a/coconut/command/util.py +++ b/coconut/command/util.py @@ -360,7 +360,7 @@ def handling_errors(self, all_errors_exit=False): yield except SystemExit as err: self.exit(err.code) - except: + except BaseException: traceback.print_exc() if all_errors_exit: self.exit(1) From 4909b0b3e5c230d8fb5b8d5af8bc9ea6f4827857 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 4 Mar 2017 19:34:58 -0800 Subject: [PATCH 052/176] Updates to new MyPy --- coconut/command/mypy.py | 32 +++++++++----------------------- coconut/constants.py | 2 +- 2 files changed, 10 insertions(+), 24 deletions(-) diff --git a/coconut/command/mypy.py b/coconut/command/mypy.py index 3d3d33f50..f527462c7 100644 --- a/coconut/command/mypy.py +++ b/coconut/command/mypy.py @@ -19,17 +19,12 @@ from coconut.root import * # NOQA -import sys import traceback -try: - from io import StringIO -except ImportError: - from StringIO import StringIO from coconut.exceptions import CoconutException try: - from mypy.main import main + from mypy.api import run except ImportError: raise CoconutException("--mypy flag requires MyPy library", extra="run 'pip install coconut[mypy]' to fix") @@ -41,22 +36,13 @@ def mypy_run(args): """Runs mypy with given arguments and shows the result.""" - argv, sys.argv = sys.argv, [""] + args - stdout, sys.stdout = sys.stdout, StringIO() - stderr, sys.stderr = sys.stderr, StringIO() - try: - main(None) - except SystemExit: - pass + stdout, stderr, exit_code = run(args) except BaseException: - traceback.print_exc() - - out = [] - for line in sys.stdout.getvalue().splitlines(): - out.append((line, False)) - for line in sys.stderr.getvalue().splitlines(): - out.append((line, True)) - - sys.argv, sys.stdout, sys.stderr = argv, stdout, stderr - return out + return traceback.format_exc() + lines = [] + for line in stdout.splitlines(): + lines.append(line) + for line in stderr.splitlines(): + lines.append(line) + return "\n".join(lines) diff --git a/coconut/constants.py b/coconut/constants.py index c334fc57f..5ec08e878 100644 --- a/coconut/constants.py +++ b/coconut/constants.py @@ -91,7 +91,7 @@ def fixpath(path): "jupyter": (1, 0), "jupyter-console": (5, 1), "ipython": (5, 2), - "mypy": (0, 471), + "mypy": (0, 501), "prompt_toolkit": (1, 0), "futures": (3, 0), "argparse": (1, 4), From 7eeabe00571b0e184d8b7e830bd95cb66720c117 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 4 Mar 2017 19:35:08 -0800 Subject: [PATCH 053/176] Fixes a typo --- tests/main_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/main_test.py b/tests/main_test.py index da34bdbc4..2356d7bf4 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -333,7 +333,7 @@ def test_prisoner(self): def test_pyprover(self): with remove_when_done(pyprover): - comp_pyrover() + comp_pyprover() run_pyprover() def test_pyston(self): From 2258d2081d2f784b862bc4a50d95d4e88d7bb12e Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 4 Mar 2017 19:44:36 -0800 Subject: [PATCH 054/176] Adds releases to readme --- README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.rst b/README.rst index 855d43265..5dab62e96 100644 --- a/README.rst +++ b/README.rst @@ -22,6 +22,7 @@ after which the entire world of Coconut will be at your disposal. To help you ge - FAQ_: If you have questions about who Coconut is built for and whether or not you should use it, Coconut's frequently asked questions have you covered. - `Create a New Issue `_: If you're having a problem with Coconut, creating a new issue detailing the problem will allow it to be addressed as soon as possible. - Gitter_: For any questions, concerns, or comments about anything Coconut-related, ask around at Coconut's Gitter, a GitHub-integrated chat room for Coconut developers. +- Releases_: Want to know what's been added in recent Coconut versions? Check out the release log for all the new features and fixes. *Note: If the above documentation links are not working, try the* |mirror|_ *.* @@ -34,5 +35,6 @@ __ Coconut_ .. _FAQ: http://coconut.readthedocs.org/en/master/FAQ.html .. _GitHub: https://github.com/evhub/coconut .. _Gitter: https://gitter.im/evhub/coconut +.. _Releases: https://github.com/evhub/coconut/releases .. _mirror: http://pythonhosted.org/coconut/ .. |mirror| replace:: *mirror* From f369d7f8ab090b9e90cc48ee45fd48799fc54ed0 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 4 Mar 2017 19:44:44 -0800 Subject: [PATCH 055/176] Updates ipython version --- coconut/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coconut/constants.py b/coconut/constants.py index 5ec08e878..87bfd9337 100644 --- a/coconut/constants.py +++ b/coconut/constants.py @@ -90,7 +90,7 @@ def fixpath(path): "psutil": (5, 1), "jupyter": (1, 0), "jupyter-console": (5, 1), - "ipython": (5, 2), + "ipython": (5, 3), "mypy": (0, 501), "prompt_toolkit": (1, 0), "futures": (3, 0), From 34d40493b1ca90f9f82ac68729ae03884e63d46a Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 4 Mar 2017 20:00:07 -0800 Subject: [PATCH 056/176] Changes test command running system --- tests/main_test.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/main_test.py b/tests/main_test.py index 2356d7bf4..23419ede2 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -22,11 +22,12 @@ import unittest import sys import os -import subprocess import shutil import platform from contextlib import contextmanager +from coconut.command.util import run_cmd + #----------------------------------------------------------------------------------------------------------------------- # CONSTANTS: #----------------------------------------------------------------------------------------------------------------------- @@ -79,8 +80,7 @@ def call(cmd, assert_output=False, check_mypy=None, **kwargs): check_mypy = all("extras" not in arg for arg in cmd) print("\n>", (cmd if isinstance(cmd, str) else " ".join(cmd))) lines = [] - for raw_line in subprocess.Popen(cmd, stdout=subprocess.PIPE, **kwargs).stdout.readlines(): - line = raw_line.rstrip().decode(sys.stdout.encoding) + for line in run_cmd(cmd, show_output=False).readlines(): print(line) lines.append(line) for line in lines: @@ -227,18 +227,18 @@ def comp_pyston(args=[]): def run_pyston(): """Runs pyston.""" - call(["python", os.path.join(os.curdir, "pyston", "runner.py")], assert_output=True) + call(["python", os.path.join(pyston, "runner.py")], assert_output=True) def comp_pyprover(args=[]): """Compiles evhub/pyprover.""" call(["git", "clone", pyprover_git]) - call_coconut(["pyprover-source", "pyprover", "--strict"] + args) + call_coconut([os.path.join("pyprover", "pyprover-source"), os.path.join("pyprover", "pyprover"), "--strict"] + args) def run_pyprover(args=[]): """Runs pyprover.""" - call(["python", os.path.join(os.curdir, "pyprover", "tests.py")], assert_output=True) + call(["python", os.path.join(pyprover, "pyprover", "tests.py")], assert_output=True) def comp_all(args=[]): From d0ab4b18563c4a73e4e7f388326a37f6684df5a7 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 4 Mar 2017 20:03:45 -0800 Subject: [PATCH 057/176] Fixes test typo --- tests/main_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/main_test.py b/tests/main_test.py index 23419ede2..b2867ec5e 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -80,7 +80,7 @@ def call(cmd, assert_output=False, check_mypy=None, **kwargs): check_mypy = all("extras" not in arg for arg in cmd) print("\n>", (cmd if isinstance(cmd, str) else " ".join(cmd))) lines = [] - for line in run_cmd(cmd, show_output=False).readlines(): + for line in run_cmd(cmd, show_output=False).splitlines(): print(line) lines.append(line) for line in lines: From 660995a5f89bad54722e3203c2162c64220426f8 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 4 Mar 2017 20:18:45 -0800 Subject: [PATCH 058/176] Fixes command running --- coconut/command/util.py | 17 +++++++++-------- tests/main_test.py | 16 ++++++++++------ 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/coconut/command/util.py b/coconut/command/util.py index 057128ebe..387a670a4 100644 --- a/coconut/command/util.py +++ b/coconut/command/util.py @@ -224,22 +224,23 @@ def call_output(cmd): yield err.decode(get_encoding(sys.stderr)) -def run_cmd(cmd, show_output=True, raise_errs=True): +def run_cmd(cmd, show_output=True, raise_errs=True, auto_which=True): """Runs a console command.""" if not cmd or not isinstance(cmd, list): raise CoconutInternalException("console commands must be passed as non-empty lists") else: - try: - from shutil import which - except ImportError: - pass - else: - cmd[0] = which(cmd[0]) or cmd[0] + if auto_which: + try: + from shutil import which + except ImportError: + pass + else: + cmd[0] = which(cmd[0]) or cmd[0] logger.log_cmd(cmd) if show_output and raise_errs: return subprocess.check_call(cmd) elif raise_errs: - return subprocess.check_output(cmd, stderr=subprocess.STDOUT) + return subprocess.check_output(cmd, stderr=subprocess.STDOUT).decode(get_encoding(sys.stdout)) elif show_output: return subprocess.call(cmd) else: diff --git a/tests/main_test.py b/tests/main_test.py index b2867ec5e..a31d4a7ec 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -72,21 +72,25 @@ def escape(inputstring): return inputstring.replace("$", "\\$").replace("`", "\\`") -def call(cmd, assert_output=False, check_mypy=None, **kwargs): +def call(cmd, assert_output=False, check_mypy=None, check_errors=None, **kwargs): """Executes a shell command.""" + print("\n>", " ".join(cmd)) if assert_output is True: assert_output = "" + doing_extras = any("extras" in arg for arg in cmd) if check_mypy is None: - check_mypy = all("extras" not in arg for arg in cmd) - print("\n>", (cmd if isinstance(cmd, str) else " ".join(cmd))) + check_mypy = not doing_extras + if check_errors is None: + check_errors = not doing_extras lines = [] - for line in run_cmd(cmd, show_output=False).splitlines(): + for line in run_cmd(cmd, show_output=False, raise_errs=False, auto_which=False).splitlines(): print(line) lines.append(line) for line in lines: assert "Traceback (most recent call last):" not in line - assert "Exception" not in line - assert "Error" not in line + if check_errors: + assert "Exception" not in line + assert "Error" not in line if check_mypy and all(test not in line for test in ignore_mypy_errs_with): assert "error:" not in line if assert_output is None: From f4fb9d5cf832218b792693869aae19afaf69b31e Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 4 Mar 2017 20:35:25 -0800 Subject: [PATCH 059/176] Further fixes command running --- coconut/command/util.py | 19 +++++++++---------- tests/main_test.py | 14 ++++++++++---- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/coconut/command/util.py b/coconut/command/util.py index 387a670a4..ca1aa3c7d 100644 --- a/coconut/command/util.py +++ b/coconut/command/util.py @@ -213,9 +213,9 @@ def run_file(path): return runpy.run_path(path, run_name="__main__") -def call_output(cmd): +def call_output(cmd, **kwargs): """Run command and read output.""" - p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs) while p.poll() is None: out, err = p.communicate() if out is not None: @@ -224,18 +224,17 @@ def call_output(cmd): yield err.decode(get_encoding(sys.stderr)) -def run_cmd(cmd, show_output=True, raise_errs=True, auto_which=True): +def run_cmd(cmd, show_output=True, raise_errs=True): """Runs a console command.""" if not cmd or not isinstance(cmd, list): raise CoconutInternalException("console commands must be passed as non-empty lists") else: - if auto_which: - try: - from shutil import which - except ImportError: - pass - else: - cmd[0] = which(cmd[0]) or cmd[0] + try: + from shutil import which + except ImportError: + pass + else: + cmd[0] = which(cmd[0]) or cmd[0] logger.log_cmd(cmd) if show_output and raise_errs: return subprocess.check_call(cmd) diff --git a/tests/main_test.py b/tests/main_test.py index a31d4a7ec..ef5e0b5c0 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -26,7 +26,7 @@ import platform from contextlib import contextmanager -from coconut.command.util import run_cmd +from coconut.command.util import call_output #----------------------------------------------------------------------------------------------------------------------- # CONSTANTS: @@ -74,16 +74,22 @@ def escape(inputstring): def call(cmd, assert_output=False, check_mypy=None, check_errors=None, **kwargs): """Executes a shell command.""" - print("\n>", " ".join(cmd)) + if isinstance(cmd, str): + str_cmd = cmd + list_cmd = cmd.split() + else: + list_cmd = cmd + str_cmd = " ".join(cmd) + print("\n>", str_cmd) if assert_output is True: assert_output = "" - doing_extras = any("extras" in arg for arg in cmd) + doing_extras = any("extras" in arg for arg in list_cmd) if check_mypy is None: check_mypy = not doing_extras if check_errors is None: check_errors = not doing_extras lines = [] - for line in run_cmd(cmd, show_output=False, raise_errs=False, auto_which=False).splitlines(): + for line in "".join(call_output(cmd, **kwargs)).splitlines(): print(line) lines.append(line) for line in lines: From b83b30379f2dcf15217261b1e202bc2fccb6d872 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 4 Mar 2017 20:51:24 -0800 Subject: [PATCH 060/176] Fixes cmd output ordering --- coconut/command/util.py | 22 ++++++++++++++++------ tests/main_test.py | 2 ++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/coconut/command/util.py b/coconut/command/util.py index ca1aa3c7d..ef28ddd29 100644 --- a/coconut/command/util.py +++ b/coconut/command/util.py @@ -213,15 +213,25 @@ def run_file(path): return runpy.run_path(path, run_name="__main__") -def call_output(cmd, **kwargs): +def call_output(cmd, stderr_first=False, **kwargs): """Run command and read output.""" p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs) while p.poll() is None: - out, err = p.communicate() - if out is not None: - yield out.decode(get_encoding(sys.stdout)) - if err is not None: - yield err.decode(get_encoding(sys.stderr)) + stdout, stderr = p.communicate() + if stdout is not None: + stdout = stdout.decode(get_encoding(sys.stdout)) + if stderr is not None: + stderr = stderr.decode(get_encoding(sys.stderr)) + if stderr_first: + if stderr is not None: + yield stderr + if stdout is not None: + yield stdout + else: + if stdout is not None: + yield stdout + if stderr is not None: + yield stderr def run_cmd(cmd, show_output=True, raise_errs=True): diff --git a/tests/main_test.py b/tests/main_test.py index ef5e0b5c0..c271a8f91 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -88,6 +88,8 @@ def call(cmd, assert_output=False, check_mypy=None, check_errors=None, **kwargs) check_mypy = not doing_extras if check_errors is None: check_errors = not doing_extras + if doing_extras: + kwargs["stderr_first"] = True lines = [] for line in "".join(call_output(cmd, **kwargs)).splitlines(): print(line) From 5586a9e6c6d8a93f0dd3f2b0346b683afaa77674 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 4 Mar 2017 21:18:48 -0800 Subject: [PATCH 061/176] Installs pyprover --- tests/main_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/main_test.py b/tests/main_test.py index c271a8f91..f5fb0eb78 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -250,6 +250,7 @@ def comp_pyprover(args=[]): def run_pyprover(args=[]): """Runs pyprover.""" + call(["pip", "install", "-e", pyprover]) call(["python", os.path.join(pyprover, "pyprover", "tests.py")], assert_output=True) From 06dc428a04302dffca82b8851131a4dc6d469b0b Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 4 Mar 2017 22:44:19 -0800 Subject: [PATCH 062/176] Fixes mypy usage --- coconut/command/mypy.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/coconut/command/mypy.py b/coconut/command/mypy.py index f527462c7..a033235ba 100644 --- a/coconut/command/mypy.py +++ b/coconut/command/mypy.py @@ -40,9 +40,7 @@ def mypy_run(args): stdout, stderr, exit_code = run(args) except BaseException: return traceback.format_exc() - lines = [] for line in stdout.splitlines(): - lines.append(line) + yield line, False for line in stderr.splitlines(): - lines.append(line) - return "\n".join(lines) + yield line, True From f47041541decdbcf633158f858aa787c6c7dabbd Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sun, 5 Mar 2017 00:08:16 -0800 Subject: [PATCH 063/176] Further fixes command calling --- coconut/command/util.py | 33 +++++++++++++------------------ tests/main_test.py | 43 +++++++++++++++++------------------------ 2 files changed, 31 insertions(+), 45 deletions(-) diff --git a/coconut/command/util.py b/coconut/command/util.py index ef28ddd29..e17d9066b 100644 --- a/coconut/command/util.py +++ b/coconut/command/util.py @@ -213,25 +213,19 @@ def run_file(path): return runpy.run_path(path, run_name="__main__") -def call_output(cmd, stderr_first=False, **kwargs): +def call_output(cmd, **kwargs): """Run command and read output.""" p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs) - while p.poll() is None: - stdout, stderr = p.communicate() - if stdout is not None: - stdout = stdout.decode(get_encoding(sys.stdout)) - if stderr is not None: - stderr = stderr.decode(get_encoding(sys.stderr)) - if stderr_first: - if stderr is not None: - yield stderr - if stdout is not None: - yield stdout - else: - if stdout is not None: - yield stdout - if stderr is not None: - yield stderr + retcode = p.poll() + stdout, stderr = [], [] + while retcode is None: + out, err = p.communicate() + if out is not None: + stdout.append(out.decode(get_encoding(sys.stdout))) + if err is not None: + stderr.append(err.decode(get_encoding(sys.stderr))) + retcode = p.poll() + return stdout, stderr, retcode def run_cmd(cmd, show_output=True, raise_errs=True): @@ -248,12 +242,11 @@ def run_cmd(cmd, show_output=True, raise_errs=True): logger.log_cmd(cmd) if show_output and raise_errs: return subprocess.check_call(cmd) - elif raise_errs: - return subprocess.check_output(cmd, stderr=subprocess.STDOUT).decode(get_encoding(sys.stdout)) elif show_output: return subprocess.call(cmd) else: - return "".join(call_output(cmd)) + stdout, stderr, _ = call_output(cmd) + return "".join(stdout + stderr) def set_mypy_path(mypy_path): diff --git a/tests/main_test.py b/tests/main_test.py index f5fb0eb78..b2a14da24 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -72,31 +72,23 @@ def escape(inputstring): return inputstring.replace("$", "\\$").replace("`", "\\`") -def call(cmd, assert_output=False, check_mypy=None, check_errors=None, **kwargs): +def call(cmd, assert_output=False, check_mypy=False, check_errors=False, stderr_first=False, **kwargs): """Executes a shell command.""" - if isinstance(cmd, str): - str_cmd = cmd - list_cmd = cmd.split() - else: - list_cmd = cmd - str_cmd = " ".join(cmd) - print("\n>", str_cmd) + print("\n>", (cmd if isinstance(cmd, str) else " ".join(cmd))) if assert_output is True: assert_output = "" - doing_extras = any("extras" in arg for arg in list_cmd) - if check_mypy is None: - check_mypy = not doing_extras - if check_errors is None: - check_errors = not doing_extras - if doing_extras: - kwargs["stderr_first"] = True - lines = [] - for line in "".join(call_output(cmd, **kwargs)).splitlines(): + stdout, stderr, retcode = call_output(cmd, **kwargs) + assert not retcode + if stderr_first: + out = stderr + stdout + else: + out = stdout + stderr + lines = "".join(out).splitlines() + for line in lines: print(line) - lines.append(line) for line in lines: - assert "Traceback (most recent call last):" not in line if check_errors: + assert "Traceback (most recent call last):" not in line assert "Exception" not in line assert "Error" not in line if check_mypy and all(test not in line for test in ignore_mypy_errs_with): @@ -107,14 +99,14 @@ def call(cmd, assert_output=False, check_mypy=None, check_errors=None, **kwargs) assert lines and assert_output in lines[-1] -def call_coconut(args): +def call_coconut(args, **kwargs): """Calls Coconut.""" if "--jobs" not in args and platform.python_implementation() != "PyPy": args = ["--jobs", "sys"] + args - call(["coconut"] + args) + call(["coconut"] + args, **kwargs) -def comp(path=None, folder=None, file=None, args=[]): +def comp(path=None, folder=None, file=None, args=[], **kwargs): """Compiles a test file or directory.""" paths = [] if path is not None: @@ -125,7 +117,7 @@ def comp(path=None, folder=None, file=None, args=[]): if file is not None: paths.append(file) source = os.path.join(src, *paths) - call_coconut([source, compdest] + args) + call_coconut([source, compdest] + args, **kwargs) @contextmanager @@ -158,7 +150,7 @@ def using_dest(): def comp_extras(args=[]): """Compiles extras.coco.""" - comp(file="extras.coco", args=args) + comp(file="extras.coco", args=args, check_mypy=False, check_errors=False, stderr_first=True) def comp_runner(args=[]): @@ -245,10 +237,11 @@ def run_pyston(): def comp_pyprover(args=[]): """Compiles evhub/pyprover.""" call(["git", "clone", pyprover_git]) + call_coconut([os.path.join("pyprover", "setup.py"), "--strict"] + args) call_coconut([os.path.join("pyprover", "pyprover-source"), os.path.join("pyprover", "pyprover"), "--strict"] + args) -def run_pyprover(args=[]): +def run_pyprover(): """Runs pyprover.""" call(["pip", "install", "-e", pyprover]) call(["python", os.path.join(pyprover, "pyprover", "tests.py")], assert_output=True) From 9b520a0f738141163401823e172aec4a8eeab4d0 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sun, 5 Mar 2017 00:49:16 -0800 Subject: [PATCH 064/176] Further fixes test command running --- coconut/command/util.py | 3 +-- tests/main_test.py | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/coconut/command/util.py b/coconut/command/util.py index e17d9066b..ec3a4623b 100644 --- a/coconut/command/util.py +++ b/coconut/command/util.py @@ -216,8 +216,7 @@ def run_file(path): def call_output(cmd, **kwargs): """Run command and read output.""" p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs) - retcode = p.poll() - stdout, stderr = [], [] + stdout, stderr, retcode = [], [], None while retcode is None: out, err = p.communicate() if out is not None: diff --git a/tests/main_test.py b/tests/main_test.py index b2a14da24..66d1a6103 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -150,7 +150,7 @@ def using_dest(): def comp_extras(args=[]): """Compiles extras.coco.""" - comp(file="extras.coco", args=args, check_mypy=False, check_errors=False, stderr_first=True) + comp(file="extras.coco", args=args) def comp_runner(args=[]): @@ -185,7 +185,7 @@ def run_src(): def run_extras(): """Runs extras.py.""" - call(["python", os.path.join(dest, "extras.py")], assert_output=True) + call(["python", os.path.join(dest, "extras.py")], assert_output=True, check_mypy=False, check_errors=False, stderr_first=True) def run(args=[], agnostic_target=None, use_run_arg=False): @@ -205,13 +205,13 @@ def run(args=[], agnostic_target=None, use_run_arg=False): comp_35(args) comp_agnostic(agnostic_args) if use_run_arg: - comp_runner(["--run"] + agnostic_args) + comp_runner(["--run"] + agnostic_args, assert_output=True) else: comp_runner(agnostic_args) run_src() if use_run_arg: - comp_extras(["--run"] + agnostic_args) + comp_extras(["--run"] + agnostic_args, assert_output=True, check_mypy=False, check_errors=False, stderr_first=True) else: comp_extras(agnostic_args) run_extras() From 1731abe1ab05e7c91b51738814a0071df9f91ec9 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sun, 5 Mar 2017 02:00:37 -0800 Subject: [PATCH 065/176] Fixes keyword argument usage --- tests/main_test.py | 68 +++++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/tests/main_test.py b/tests/main_test.py index 66d1a6103..31aea5ec2 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -148,44 +148,44 @@ def using_dest(): #----------------------------------------------------------------------------------------------------------------------- -def comp_extras(args=[]): +def comp_extras(args=[], **kwargs): """Compiles extras.coco.""" - comp(file="extras.coco", args=args) + comp(file="extras.coco", args=args, **kwargs) -def comp_runner(args=[]): +def comp_runner(args=[], **kwargs): """Compiles runner.coco.""" - comp(file="runner.coco", args=args) + comp(file="runner.coco", args=args, **kwargs) -def comp_agnostic(args=[]): +def comp_agnostic(args=[], **kwargs): """Compiles agnostic.""" - comp(path="cocotest", folder="agnostic", args=args) + comp(path="cocotest", folder="agnostic", args=args, **kwargs) -def comp_2(args=[]): +def comp_2(args=[], **kwargs): """Compiles python2.""" - comp(path="cocotest", folder="python2", args=["--target", "2"] + args) + comp(path="cocotest", folder="python2", args=["--target", "2"] + args, **kwargs) -def comp_3(args=[]): +def comp_3(args=[], **kwargs): """Compiles python3.""" - comp(path="cocotest", folder="python3", args=["--target", "3"] + args) + comp(path="cocotest", folder="python3", args=["--target", "3"] + args, **kwargs) -def comp_35(args=[]): +def comp_35(args=[], **kwargs): """Compiles python35.""" - comp(path="cocotest", folder="python35", args=["--target", "35"] + args) + comp(path="cocotest", folder="python35", args=["--target", "35"] + args, **kwargs) -def run_src(): +def run_src(**kwargs): """Runs runner.py.""" - call(["python", os.path.join(dest, "runner.py")], assert_output=True) + call(["python", os.path.join(dest, "runner.py")], assert_output=True, **kwargs) -def run_extras(): +def run_extras(**kwargs): """Runs extras.py.""" - call(["python", os.path.join(dest, "extras.py")], assert_output=True, check_mypy=False, check_errors=False, stderr_first=True) + call(["python", os.path.join(dest, "extras.py")], assert_output=True, check_mypy=False, check_errors=False, stderr_first=True, **kwargs) def run(args=[], agnostic_target=None, use_run_arg=False): @@ -217,48 +217,48 @@ def run(args=[], agnostic_target=None, use_run_arg=False): run_extras() -def comp_prisoner(args=[]): +def comp_prisoner(args=[], **kwargs): """Compiles evhub/prisoner.""" call(["git", "clone", prisoner_git]) - call_coconut(["prisoner", "--strict"] + args) + call_coconut(["prisoner", "--strict"] + args, **kwargs) -def comp_pyston(args=[]): +def comp_pyston(args=[], **kwargs): """Compiles evhub/pyston.""" call(["git", "clone", pyston_git]) - call_coconut(["pyston"] + args) + call_coconut(["pyston"] + args, **kwargs) -def run_pyston(): +def run_pyston(**kwargs): """Runs pyston.""" - call(["python", os.path.join(pyston, "runner.py")], assert_output=True) + call(["python", os.path.join(pyston, "runner.py")], assert_output=True, **kwargs) -def comp_pyprover(args=[]): +def comp_pyprover(args=[], **kwargs): """Compiles evhub/pyprover.""" call(["git", "clone", pyprover_git]) - call_coconut([os.path.join("pyprover", "setup.py"), "--strict"] + args) - call_coconut([os.path.join("pyprover", "pyprover-source"), os.path.join("pyprover", "pyprover"), "--strict"] + args) + call_coconut([os.path.join("pyprover", "setup.py"), "--strict"] + args, **kwargs) + call_coconut([os.path.join("pyprover", "pyprover-source"), os.path.join("pyprover", "pyprover"), "--strict"] + args, **kwargs) -def run_pyprover(): +def run_pyprover(**kwargs): """Runs pyprover.""" call(["pip", "install", "-e", pyprover]) - call(["python", os.path.join(pyprover, "pyprover", "tests.py")], assert_output=True) + call(["python", os.path.join(pyprover, "pyprover", "tests.py")], assert_output=True, **kwargs) -def comp_all(args=[]): +def comp_all(args=[], **kwargs): """Compile Coconut tests.""" try: os.mkdir(dest) except Exception: pass - comp_2(args) - comp_3(args) - comp_35(args) - comp_agnostic(args) - comp_runner(args) - comp_extras(args) + comp_2(args, **kwargs) + comp_3(args, **kwargs) + comp_35(args, **kwargs) + comp_agnostic(args, **kwargs) + comp_runner(args, **kwargs) + comp_extras(args, **kwargs) #----------------------------------------------------------------------------------------------------------------------- # TESTS: From 646e52d1f97a9ae79794ea9ef19634771490250d Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sun, 5 Mar 2017 22:35:22 -0800 Subject: [PATCH 066/176] Further fixes tests --- tests/main_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/main_test.py b/tests/main_test.py index 31aea5ec2..7c8ce4bea 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -78,7 +78,6 @@ def call(cmd, assert_output=False, check_mypy=False, check_errors=False, stderr_ if assert_output is True: assert_output = "" stdout, stderr, retcode = call_output(cmd, **kwargs) - assert not retcode if stderr_first: out = stderr + stdout else: @@ -86,6 +85,7 @@ def call(cmd, assert_output=False, check_mypy=False, check_errors=False, stderr_ lines = "".join(out).splitlines() for line in lines: print(line) + assert not retcode for line in lines: if check_errors: assert "Traceback (most recent call last):" not in line From d64954dbcbd7e1a516b06f3b9f3b148b8e85729e Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Mon, 6 Mar 2017 00:31:53 -0800 Subject: [PATCH 067/176] Update dependencies --- Makefile | 4 ++-- coconut/constants.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 40ec9c06d..39539f753 100644 --- a/Makefile +++ b/Makefile @@ -5,8 +5,8 @@ install: .PHONY: dev dev: - pip3 install --upgrade setuptools pip - pip3 install --upgrade -e .[dev] + pip install --upgrade setuptools pip + pip install --upgrade -e .[dev] pre-commit install -f --install-hooks .PHONY: format diff --git a/coconut/constants.py b/coconut/constants.py index 87bfd9337..5b2e60c87 100644 --- a/coconut/constants.py +++ b/coconut/constants.py @@ -81,13 +81,13 @@ def fixpath(path): } req_vers = { - "pyparsing": (2, 1, 10), + "pyparsing": (2, 2, 0), "pre-commit": (0, 13), "sphinx": (1, 5), "pygments": (2, 2), "recommonmark": (0, 4), "sphinx_bootstrap_theme": (0, 4), - "psutil": (5, 1), + "psutil": (5, 2), "jupyter": (1, 0), "jupyter-console": (5, 1), "ipython": (5, 3), From 3a1dd1362846091093f836216a2b3ff2b2ddfde4 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Mon, 6 Mar 2017 00:53:46 -0800 Subject: [PATCH 068/176] Improves tests --- .travis.yml | 1 + tests/main_test.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4c40cb213..e08a18370 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ python: - '3.3' - '3.4' - '3.5' +- '3.6' - pypy3 install: - pip install "pip==7.1.2" diff --git a/tests/main_test.py b/tests/main_test.py index 7c8ce4bea..8db9f6c7f 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -237,7 +237,7 @@ def run_pyston(**kwargs): def comp_pyprover(args=[], **kwargs): """Compiles evhub/pyprover.""" call(["git", "clone", pyprover_git]) - call_coconut([os.path.join("pyprover", "setup.py"), "--strict"] + args, **kwargs) + call_coconut([os.path.join("pyprover", "setup.coco"), "--strict"] + args, **kwargs) call_coconut([os.path.join("pyprover", "pyprover-source"), os.path.join("pyprover", "pyprover"), "--strict"] + args, **kwargs) From 5c9afbaf0ef8ed7d159cab9fcb83ec657823002e Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Wed, 8 Mar 2017 23:52:52 -0800 Subject: [PATCH 069/176] Fixes displaying jupyter errors Resolves #234. --- coconut/icoconut/root.py | 13 ++++++++++++- coconut/root.py | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/coconut/icoconut/root.py b/coconut/icoconut/root.py index 5e7b8d002..defac3fd5 100644 --- a/coconut/icoconut/root.py +++ b/coconut/icoconut/root.py @@ -19,6 +19,8 @@ from coconut.root import * # NOQA +import traceback + from ipykernel.ipkernel import IPythonKernel from ipykernel.zmqshell import ZMQInteractiveShell from IPython.core.inputsplitter import IPythonInputSplitter @@ -42,7 +44,7 @@ # GLOBALS: #----------------------------------------------------------------------------------------------------------------------- -COMPILER = Compiler(target="sys") +COMPILER = Compiler(target="sys", line_numbers=True, keep_lines=True) RUNNER = Runner(COMPILER) parse_block_memo = {} @@ -88,6 +90,15 @@ def ast_parse(self, source, *args, **kwargs): else: return super(CoconutCompiler, self).ast_parse(compiled, *args, **kwargs) + def cache(self, code, *args, **kwargs): + """Version of cache that compiles Coconut code first.""" + try: + compiled = memoized_parse_sys(code) + except CoconutException: + traceback.print_exc() + else: + return super(CoconutCompiler, self).cache(compiled, *args, **kwargs) + class CoconutSplitter(IPythonInputSplitter, object): """IPython splitter for Coconut.""" diff --git a/coconut/root.py b/coconut/root.py index 2368ac881..e93a6944d 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -26,7 +26,7 @@ VERSION = "1.2.2" VERSION_NAME = "Colonel" # False for release, int >= 1 for develop -DEVELOP = 4 +DEVELOP = 5 #----------------------------------------------------------------------------------------------------------------------- # CONSTANTS: From 607573d584065e0977a8b61cc96127cac607e96a Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Wed, 15 Mar 2017 17:38:55 -0700 Subject: [PATCH 070/176] Adds fmap built-in Refs #237. --- DOCS.md | 88 ++++++++++++++++------- HELP.md | 3 + coconut/compiler/header.py | 3 + coconut/constants.py | 2 + tests/src/cocotest/agnostic/suite.coco | 3 + tests/src/cocotest/agnostic/tutorial.coco | 35 ++++----- tests/src/cocotest/agnostic/util.coco | 3 + 7 files changed, 95 insertions(+), 42 deletions(-) diff --git a/DOCS.md b/DOCS.md index 893e493e1..d95706d6d 100644 --- a/DOCS.md +++ b/DOCS.md @@ -54,6 +54,7 @@ 1. [In-line `global` And `nonlocal` Assignment](#in-line-global-and-nonlocal-assignment) 1. [Code Passthrough](#code-passthrough) 1. [Built-Ins](#built-ins) + 1. [Enhanced Built-Ins](#enhanced-built-ins) 1. [`addpattern`](#addpattern) 1. [`prepattern`](#prepattern) 1. [`reduce`](#reduce) @@ -62,8 +63,8 @@ 1. [`tee`](#tee) 1. [`consume`](#consume) 1. [`count`](#count) - 1. [Enhanced Built-Ins](#enhanced-built-ins) 1. [`datamaker`](#datamaker) + 1. [`fmap`](#fmap) 1. [`recursive_iterator`](#recursiveiterator) 1. [`parallel_map`](#parallelmap) 1. [`concurrent_map`](#concurrentmap) @@ -1244,7 +1245,6 @@ data Node(left, right) ###### Python ```coconut_python import collections - class Empty(collections.namedtuple("Empty", "")): __slots__ = () class Leaf(collections.namedtuple("Leaf", "n")): @@ -1289,6 +1289,27 @@ cdef f(x): ## Built-Ins +### Enhanced Built-Ins + +Coconut's `map`, `zip`, `filter`, `reversed`, and `enumerate` objects are enhanced versions of their Python equivalents that support `reversed`, `repr`, optimized normal (and iterator) slicing (all but `filter`), `len` (all but `filter`), and have added attributes which subclasses can make use of to get at the original arguments to the object: + +- `map`: `_func`, `_iters` +- `zip`: `_iters` +- `filter`: `_func`, `_iter` +- `reversed`: `_iter` +- `enumerate`: `_iter`, `_start` + +##### Example + +###### Coconut +```coconut +map((+), range(5), range(6)) |> len |> print +range(10) |> filter$((x) -> x < 5) |> reversed |> tuple |> print +``` + +###### Python +_Can't be done without defining a custom `map` type. The full definition of `map` can be found in the Coconut header._ + ### `addpattern` Takes one argument that is a [pattern-matching function](#pattern-matching-functions), and returns a decorator that adds the patterns in the existing function to the new function being decorated, where the existing patterns are checked first, then the new. Roughly equivalent to: @@ -1320,7 +1341,9 @@ _Can't be done without a complicated decorator definition and a long series of c ### `prepattern` -Takes one argument that is a [pattern-matching function](#pattern-matching-functions), and returns a decorator that adds the patterns in the existing function to the new function being decorated, where the new patterns are checked first, then the existing. Equivalent to: +Takes one argument that is a [pattern-matching function](#pattern-matching-functions), and returns a decorator that adds the patterns in the existing function to the new function being decorated, where the new patterns are checked first, then the existing. + +Equivalent to: ``` def prepattern(base_func): """Decorator to add a new case to a pattern-matching function, where the new case is checked first.""" @@ -1477,7 +1500,9 @@ sliced = itertools.islice(temp, 5, None) ### `consume` -Coconut provides the `consume` function to efficiently exhaust an iterator and thus perform any lazy evaluation contained within it. `consume` takes one optional argument, `keep_last`, that defaults to 0 and specifies how many, if any, items from the end to return as an iterable (`None` will keep all elements). Equivalent to: +Coconut provides the `consume` function to efficiently exhaust an iterator and thus perform any lazy evaluation contained within it. `consume` takes one optional argument, `keep_last`, that defaults to 0 and specifies how many, if any, items from the end to return as an iterable (`None` will keep all elements). + +Equivalent to: ```coconut def consume(iterable, keep_last=0): """Fully exhaust iterable and return the last keep_last elements.""" @@ -1529,52 +1554,65 @@ count()$[10**100] |> print ###### Python _Can't be done quickly without Coconut's iterator slicing, which requires many complicated pieces. The necessary definitions in Python can be found in the Coconut header._ -### Enhanced Built-Ins +### `datamaker` -Coconut's `map`, `zip`, `filter`, `reversed`, and `enumerate` objects are enhanced versions of their Python equivalents that support `reversed`, `repr`, optimized normal (and iterator) slicing (all but `filter`), `len` (all but `filter`), and have added attributes which subclasses can make use of to get at the original arguments to the object: +Coconut provides the `datamaker` function to allow direct access to the base constructor of data types created with the Coconut `data` statement. This is particularly useful when writing alternative constructors for data types by overwriting `__new__`. -- `map`: `_func`, `_iters` -- `zip`: `_iters` -- `filter`: `_func`, `_iter` -- `reversed`: `_iter` -- `enumerate`: `_iter`, `_start` +Equivalent to: +```coconut +def datamaker(data_type): + """Returns base data constructor of data_type.""" + return super(data_type, data_type).__new__$(data_type) +``` ##### Example ###### Coconut ```coconut -map((+), range(5), range(6)) |> len |> print -range(10) |> filter$((x) -> x < 5) |> reversed |> tuple |> print +data Tuple(elems): + def __new__(cls, *elems): + return elems |> datamaker(cls) ``` ###### Python -_Can't be done without defining a custom `map` type. The full definition of `map` can be found in the Coconut header._ +```coconut_python +import collections +class Tuple(collections.namedtuple("Tuple", "elems")): + __slots__ = () + def __new__(cls, *elems): + return super(cls, cls).__new__(cls, elems) +``` -### `datamaker` +### `fmap` + +In functional programming, `fmap(func, obj)` takes a data type `obj` and returns a new data type with `func` mapped over the contents. Coconut's `fmap` function does the exact same thing in Coconut. -Coconut provides the `datamaker` function to allow direct access to the base constructor of data types created with the Coconut `data` statement. This is particularly useful when writing alternative constructors for data types by overwriting `__new__`. Equivalent to: +Equivalent to: ```coconut -def datamaker(data_type): - """Returns base data constructor of data_type.""" - return super(data_type, data_type).__new__$(data_type) +def fmap(func, obj): + """Creates a copy of obj with func applied to its contents.""" + return obj |> map$(func) |*> obj.__class__ ``` ##### Example ###### Coconut ```coconut -data trilen(h): - def __new__(cls, a, b): - return (a**2 + b**2)**0.5 |> datamaker(cls) +data Nothing() +data Just(n) +Just(3) |> fmap$(x -> x*2) == Just(6) +Nothing() |> fmap$(x -> x*2) == Nothing() ``` ###### Python ```coconut_python import collections -class trilen(collections.namedtuple("trilen", "h")): +class Nothing(collections.namedtuple("Nothing", "")): + __slots__ = () +class Just(collections.namedtuple("Just", "n")): __slots__ = () - def __new__(cls, a, b): - return super(cls, cls).__new__(cls, (a**2 + b**2)**0.5) +Just(*map(lambda x: x*2, Just(3))) == Just(6) +Nothing(*map(lambda x: x*2, Nothing())) == Nothing() ``` ### `recursive_iterator` diff --git a/HELP.md b/HELP.md index dfce95cd2..53e4578fb 100644 --- a/HELP.md +++ b/HELP.md @@ -505,6 +505,7 @@ data vector2(x, y): # Test cases: vector2(1, 2) |> print # vector2(x=1, y=2) vector2(3, 4) |> abs |> print # 5 +vector2(1, 2) |> fmap$(x -> x*2) |> print # vector2(x=2, y=4) v = vector2(2, 3) v.x = 7 # AttributeError ``` @@ -516,6 +517,8 @@ data (): ``` where `` and `` are the same as the equivalent `class` definition, but `` are the different attributes of the data type, in order that the constructor should take them as arguments. In this case, `vector2` is a data type of two attributes, `x` and `y`, with one defined method, `__abs__`, that computes the magnitude. As the test cases show, we can then create, print, but _not modify_ instances of `vector2`. +One other thing to call attention to here is the use of `fmap`. `fmap` is a Coconut built-in that allows you to map functions over algebraic data types. In fact, Coconut's `data` types support iteration, so the standard `map` works on them, but it doesn't return another object of the same data type. Thus, `fmap` is simply `map` plus a call to the object's constructor. + ### n-Vector Constructor Now that we've got the 2-vector under our belt, let's move to back to our original, more complicated problem: n-vectors, that is, vectors of arbitrary length. We're going to try to make our n-vector support all the basic vector operations, but we'll start out with just the `data` definition and the constructor: diff --git a/coconut/compiler/header.py b/coconut/compiler/header.py index 8067c1fe8..92bf369f5 100644 --- a/coconut/compiler/header.py +++ b/coconut/compiler/header.py @@ -512,6 +512,9 @@ def datamaker(data_type): def consume(iterable, keep_last=0): """Fully exhaust iterable and return the last keep_last elements.""" return _coconut.collections.deque(iterable, maxlen=keep_last) # fastest way to exhaust an iterator +def fmap(func, obj): + """Creates a copy of obj with func applied to its contents.""" + return obj.__class__(*_coconut_map(func, obj)) _coconut_MatchError, _coconut_count, _coconut_enumerate, _coconut_reversed, _coconut_map, _coconut_tee, reduce, takewhile, dropwhile = MatchError, count, enumerate, reversed, map, tee, _coconut.functools.reduce, _coconut.itertools.takewhile, _coconut.itertools.dropwhile ''' else: diff --git a/coconut/constants.py b/coconut/constants.py index 5b2e60c87..bb6e89091 100644 --- a/coconut/constants.py +++ b/coconut/constants.py @@ -183,6 +183,7 @@ def fixpath(path): "addpattern", "prepattern", "recursive_iterator", + "fmap", "data keyword", "match keyword", "case keyword", @@ -415,6 +416,7 @@ def fixpath(path): "prepattern", "recursive_iterator", "concurrent_map", + "fmap", "py_chr", "py_filter", "py_hex", diff --git a/tests/src/cocotest/agnostic/suite.coco b/tests/src/cocotest/agnostic/suite.coco index f19a0dfd8..27a33db00 100644 --- a/tests/src/cocotest/agnostic/suite.coco +++ b/tests/src/cocotest/agnostic/suite.coco @@ -283,4 +283,7 @@ def suite_test(): assert True else: assert False + assert Just(3) |> map$(-> _*2) |*> Just == Just(6) == Just(3) |> fmap$(-> _*2) + assert Nothing() |> map$(-> _*2) |*> Nothing == Nothing() == Nothing() |> fmap$(-> _*2) + assert Tuple(1, 2, 3) != Tuple(1, 2) return True diff --git a/tests/src/cocotest/agnostic/tutorial.coco b/tests/src/cocotest/agnostic/tutorial.coco index d2fc9ed55..cc13eec1e 100644 --- a/tests/src/cocotest/agnostic/tutorial.coco +++ b/tests/src/cocotest/agnostic/tutorial.coco @@ -218,8 +218,9 @@ data vector2(x, y): (self.x**2 + self.y**2)**0.5 # Test cases: -assert vector2(1, 2) |> repr == "vector2(x=1, y=2)" +assert vector2(1, 2) |> str == "vector2(x=1, y=2)" assert vector2(3, 4) |> abs == 5 +vector2(1, 2) |> fmap$(x -> x*2) |> str == "vector2(x=2, y=4)" v = vector2(2, 3) try: v.x = 7 @@ -238,8 +239,8 @@ data vector(pts): return pts |> tuple |> datamaker(cls) # accesses base constructor # Test cases: -assert vector(1, 2, 3) |> repr == "vector(pts=(1, 2, 3))" -assert vector(4, 5) |> vector |> repr == "vector(pts=(4, 5))" +assert vector(1, 2, 3) |> str == "vector(pts=(1, 2, 3))" +assert vector(4, 5) |> vector |> str == "vector(pts=(4, 5))" data vector(pts): """Immutable n-vector.""" @@ -283,16 +284,16 @@ data vector(pts): self * other # Test cases: -assert vector(1, 2, 3) |> repr == "vector(pts=(1, 2, 3))" -assert vector(4, 5) |> vector |> repr == "vector(pts=(4, 5))" +assert vector(1, 2, 3) |> str == "vector(pts=(1, 2, 3))" +assert vector(4, 5) |> vector |> str == "vector(pts=(4, 5))" assert vector(3, 4) |> abs == 5 -assert vector(1, 2) + vector(2, 3) |> repr == "vector(pts=(3, 5))" -assert vector(2, 2) - vector(0, 1) |> repr == "vector(pts=(2, 1))" -assert -vector(1, 3) |> repr == "vector(pts=(-1, -3))" +assert vector(1, 2) + vector(2, 3) |> str == "vector(pts=(3, 5))" +assert vector(2, 2) - vector(0, 1) |> str == "vector(pts=(2, 1))" +assert -vector(1, 3) |> str == "vector(pts=(-1, -3))" assert (vector(1, 2) == "string") is False assert (vector(1, 2) == vector(3, 4)) is False assert (vector(2, 4) == vector(2, 4)) is True -assert 2*vector(1, 2) |> repr == "vector(pts=(2, 4))" +assert 2*vector(1, 2) |> str == "vector(pts=(2, 4))" assert vector(1, 2) * vector(1, 3) == 7 def diagonal_line(n) = range(n+1) |> map$((i) -> (i, n-i)) @@ -312,8 +313,8 @@ assert linearized_plane()$[:3] |> list == [(0, 0), (0, 1), (1, 0)] def vector_field() = linearized_plane() |> map$((xy) -> vector(*xy)) # You'll need to bring in the vector class from earlier to make these work -assert vector_field()$[0] |> repr == "vector(pts=(0, 0))" -assert vector_field()$[2:3] |> list |> repr == "[vector(pts=(1, 0))]" +assert vector_field()$[0] |> str == "vector(pts=(0, 0))" +assert vector_field()$[2:3] |> list |> str == "[vector(pts=(1, 0))]" data vector(pts): """Immutable n-vector.""" @@ -366,8 +367,8 @@ assert diagonal_line(0) |> list == [(0, 0)] assert diagonal_line(1) |> list == [(0, 1), (1, 0)] assert linearized_plane()$[0] == (0, 0) assert linearized_plane()$[:3] |> list == [(0, 0), (0, 1), (1, 0)] -assert vector_field()$[0] |> repr == "vector(pts=(0, 0))" -assert vector_field()$[2:3] |> list |> repr == "[vector(pts=(1, 0))]" +assert vector_field()$[0] |> str == "vector(pts=(0, 0))" +assert vector_field()$[2:3] |> list |> str == "[vector(pts=(1, 0))]" import math # necessary for math.acos in .angle @@ -417,10 +418,10 @@ data vector(pts): def angle(self, other is vector) = math.acos(self.unit() * other.unit()) # Test cases: -assert vector(3, 4) / 1 |> repr == "vector(pts=(3.0, 4.0))" -assert vector(2, 4) / 2 |> repr == "vector(pts=(1.0, 2.0))" -assert vector(0, 1).unit() |> repr == "vector(pts=(0.0, 1.0))" -assert vector(5, 0).unit() |> repr == "vector(pts=(1.0, 0.0))" +assert vector(3, 4) / 1 |> str == "vector(pts=(3.0, 4.0))" +assert vector(2, 4) / 2 |> str == "vector(pts=(1.0, 2.0))" +assert vector(0, 1).unit() |> str == "vector(pts=(0.0, 1.0))" +assert vector(5, 0).unit() |> str == "vector(pts=(1.0, 0.0))" assert vector(2, 0).angle(vector(3, 0)) == 0.0 assert vector(1, 0).angle(vector(0, 2)) == math.pi/2 try: diff --git a/tests/src/cocotest/agnostic/util.coco b/tests/src/cocotest/agnostic/util.coco index af6936416..dd7c5df76 100644 --- a/tests/src/cocotest/agnostic/util.coco +++ b/tests/src/cocotest/agnostic/util.coco @@ -198,6 +198,9 @@ def is_null(item): return True else: return False +data Tuple(elems): + def __new__(cls, *elems) = + elems |> datamaker$(cls) # Factorial: def factorial1(value): From 42dc3103fce8cfee455e1cfaee633261a20368e6 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Wed, 15 Mar 2017 19:18:01 -0700 Subject: [PATCH 071/176] Improves new fmap built-in Refs #237. --- DOCS.md | 17 ++++++++-- coconut/compiler/header.py | 48 +++++++++++++++++++++------ tests/src/cocotest/agnostic/main.coco | 7 ++++ tests/src/cocotest/agnostic/util.coco | 2 +- 4 files changed, 60 insertions(+), 14 deletions(-) diff --git a/DOCS.md b/DOCS.md index d95706d6d..cf16df7da 100644 --- a/DOCS.md +++ b/DOCS.md @@ -1587,30 +1587,43 @@ class Tuple(collections.namedtuple("Tuple", "elems")): In functional programming, `fmap(func, obj)` takes a data type `obj` and returns a new data type with `func` mapped over the contents. Coconut's `fmap` function does the exact same thing in Coconut. -Equivalent to: +For non-built-ins, equivalent to: ```coconut def fmap(func, obj): """Creates a copy of obj with func applied to its contents.""" - return obj |> map$(func) |*> obj.__class__ + if hasattr(obj, "__fmap__"): + return obj.__fmap__(func) + else: + args = obj |> map$(func) + try: + return obj.__class__(*args) + except TypeError: + return obj.__class__(args) ``` ##### Example ###### Coconut ```coconut +[1, 2, 3] |> fmap$(x -> x+1) == [2, 3, 4] + data Nothing() data Just(n) + Just(3) |> fmap$(x -> x*2) == Just(6) Nothing() |> fmap$(x -> x*2) == Nothing() ``` ###### Python ```coconut_python +list(map(lambda x: x+1, [1, 2, 3])) == [2, 3, 4] + import collections class Nothing(collections.namedtuple("Nothing", "")): __slots__ = () class Just(collections.namedtuple("Just", "n")): __slots__ = () + Just(*map(lambda x: x*2, Just(3))) == Just(6) Nothing(*map(lambda x: x*2, Nothing())) == Nothing() ``` diff --git a/coconut/compiler/header.py b/coconut/compiler/header.py index 92bf369f5..a51e93aea 100644 --- a/coconut/compiler/header.py +++ b/coconut/compiler/header.py @@ -105,14 +105,14 @@ def getheader(which, target="", usehash=None): import sys as _coconut_sys, os.path as _coconut_os_path _coconut_file_path = _coconut_os_path.dirname(_coconut_os_path.abspath(__file__)) _coconut_sys.path.insert(0, _coconut_file_path) -from __coconut__ import _coconut, _coconut_MatchError, _coconut_tail_call, _coconut_tco, _coconut_igetitem, _coconut_compose, _coconut_pipe, _coconut_starpipe, _coconut_backpipe, _coconut_backstarpipe, _coconut_bool_and, _coconut_bool_or, _coconut_minus, _coconut_tee, _coconut_map, _coconut_partial +from __coconut__ import _coconut, _coconut_MatchError, _coconut_tail_call, _coconut_tco, _coconut_igetitem, _coconut_compose, _coconut_pipe, _coconut_starpipe, _coconut_backpipe, _coconut_backstarpipe, _coconut_bool_and, _coconut_bool_or, _coconut_minus, _coconut_map, _coconut_partial from __coconut__ import * _coconut_sys.path.remove(_coconut_file_path) ''' elif which == "sys": header += r''' import sys as _coconut_sys -from coconut.__coconut__ import _coconut, _coconut_MatchError, _coconut_tail_call, _coconut_tco, _coconut_igetitem, _coconut_compose, _coconut_pipe, _coconut_starpipe, _coconut_backpipe, _coconut_backstarpipe, _coconut_bool_and, _coconut_bool_or, _coconut_minus, _coconut_tee, _coconut_map, _coconut_partial +from coconut.__coconut__ import _coconut, _coconut_MatchError, _coconut_tail_call, _coconut_tco, _coconut_igetitem, _coconut_compose, _coconut_pipe, _coconut_starpipe, _coconut_backpipe, _coconut_backstarpipe, _coconut_bool_and, _coconut_bool_or, _coconut_minus, _coconut_map, _coconut_partial from coconut.__coconut__ import * ''' elif which == "__coconut__" or which == "code" or which == "file": @@ -144,10 +144,10 @@ class _coconut(object):''' import collections.abc as abc''' if target.startswith("3"): header += r''' - IndexError, NameError, ValueError, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, iter, len, list, map, min, max, next, object, range, reversed, set, slice, str, sum, super, tuple, zip, repr = IndexError, NameError, ValueError, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, iter, len, list, map, min, max, next, object, range, reversed, set, slice, str, sum, super, tuple, zip, repr''' + IndexError, NameError, TypeError, ValueError, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, iter, len, list, map, min, max, next, object, range, reversed, set, slice, str, sum, super, tuple, zip, repr = IndexError, NameError, TypeError, ValueError, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, iter, len, list, map, min, max, next, object, range, reversed, set, slice, str, sum, super, tuple, zip, repr''' else: header += r''' - IndexError, NameError, ValueError, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, iter, len, list, map, min, max, next, object, range, reversed, set, slice, str, sum, super, tuple, zip, repr, bytearray = IndexError, NameError, ValueError, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, iter, len, list, map, min, max, next, object, range, reversed, set, slice, str, sum, super, tuple, zip, staticmethod(repr), bytearray''' + IndexError, NameError, TypeError, ValueError, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, iter, len, list, map, min, max, next, object, range, reversed, set, slice, str, sum, super, tuple, zip, repr, bytearray = IndexError, NameError, TypeError, ValueError, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, iter, len, list, map, min, max, next, object, range, reversed, set, slice, str, sum, super, tuple, zip, staticmethod(repr), bytearray''' header += r''' class MatchError(Exception): """Pattern-matching error.""" @@ -231,11 +231,11 @@ def __new__(cls, iterable): if _coconut.isinstance(iterable, _coconut.range): return iterable[::-1] elif not _coconut.hasattr(iterable, "__reversed__") or _coconut.isinstance(iterable, (_coconut.list, _coconut.tuple)): - new_reversed = _coconut.object.__new__(cls) - new_reversed._iter = iterable - return new_reversed + return _coconut.object.__new__(cls) else: return _coconut.reversed(iterable) + def __init__(self, iterable): + self._iter = iterable def __iter__(self): return _coconut.reversed(self._iter) def __getitem__(self, index): @@ -267,6 +267,8 @@ def count(self, elem): def index(self, elem): """Find the index of elem in the reversed iterator.""" return _coconut.len(self._iter) - self._iter.index(elem) - 1 + def __fmap__(self, func): + return self.__class__(_coconut_map(func, self._iter)) class map(_coconut.map): __slots__ = ("_func", "_iters") if hasattr(_coconut.map, "__doc__"): @@ -292,6 +294,8 @@ def __reduce_ex__(self, _): return self.__reduce__() def __copy__(self): return self.__class__(self._func, *_coconut_map(_coconut.copy.copy, self._iters)) + def __fmap__(self, func): + return self.__class__(_coconut_compose(func, self._func), *self._iters) class parallel_map(map): """Multiprocessing implementation of map using concurrent.futures. Requires arguments to be pickleable.""" @@ -336,6 +340,8 @@ def __reduce_ex__(self, _): return self.__reduce__() def __copy__(self): return self.__class__(self._func, _coconut.copy.copy(self._iter)) + def __fmap__(self, func): + raise _coconut.TypeError("fmap not supported for filter objects") class zip(_coconut.zip): __slots__ = ("_iters",) if hasattr(_coconut.zip, "__doc__"): @@ -361,6 +367,8 @@ def __reduce_ex__(self, _): return self.__reduce__() def __copy__(self): return self.__class__(*_coconut_map(_coconut.copy.copy, self._iters)) + def __fmap__(self, func): + raise _coconut.TypeError("fmap not supported for zip objects") class enumerate(_coconut.enumerate): __slots__ = ("_iter", "_start") if hasattr(_coconut.enumerate, "__doc__"): @@ -383,7 +391,9 @@ def __reduce__(self): def __reduce_ex__(self, _): return self.__reduce__() def __copy__(self): - return self.__class__(_coconut.copy.copy(self._iter), self._start)''' + return self.__class__(_coconut.copy.copy(self._iter), self._start) + def __fmap__(self, func): + raise _coconut.TypeError("fmap not supported for enumerate objects")''' if target.startswith("3"): header += r''' class count:''' @@ -431,6 +441,8 @@ def __copy__(self): return self.__class__(self._start, self._step) def __eq__(self, other): return isinstance(other, self.__class__) and self._start == other._start and self._step == other._step + def __fmap__(self, func): + raise _coconut.TypeError("fmap not supported for count objects") def recursive_iterator(func): """Decorates a function by optimizing it for iterator recursion. Requires function arguments to be pickleable.""" @@ -488,7 +500,7 @@ def __call__(self, *args, **kwargs): if i in self._argdict: callargs.append(self._argdict[i]) elif argind >= _coconut.len(args): - raise TypeError("expected at least " + _coconut.str(self._arglen - _coconut.len(self._argdict)) + " argument(s) to " + _coconut.repr(self)) + raise _coconut.TypeError("expected at least " + _coconut.str(self._arglen - _coconut.len(self._argdict)) + " argument(s) to " + _coconut.repr(self)) else: callargs.append(args[argind]) argind += 1 @@ -514,8 +526,22 @@ def consume(iterable, keep_last=0): return _coconut.collections.deque(iterable, maxlen=keep_last) # fastest way to exhaust an iterator def fmap(func, obj): """Creates a copy of obj with func applied to its contents.""" - return obj.__class__(*_coconut_map(func, obj)) -_coconut_MatchError, _coconut_count, _coconut_enumerate, _coconut_reversed, _coconut_map, _coconut_tee, reduce, takewhile, dropwhile = MatchError, count, enumerate, reversed, map, tee, _coconut.functools.reduce, _coconut.itertools.takewhile, _coconut.itertools.dropwhile + if _coconut.hasattr(obj, "__fmap__"): + return obj.__fmap__(func) + args = _coconut_map(func, obj) + if _coconut.isinstance(obj, _coconut.map): + return args + elif _coconut.isinstance(obj, _coconut.str): + return "".join(args) + elif _coconut.isinstance(obj, _coconut.dict): + args = _coconut_zip(args, obj.values()) + elif not _coconut.isinstance(obj, (_coconut.tuple, _coconut.list, _coconut.set, _coconut.frozenset)): + try: + return obj.__class__(*args) + except _coconut.TypeError: + pass + return obj.__class__(args) +_coconut_MatchError, _coconut_count, _coconut_enumerate, _coconut_reversed, _coconut_map, _coconut_tee, _coconut_zip, reduce, takewhile, dropwhile = MatchError, count, enumerate, reversed, map, tee, zip, _coconut.functools.reduce, _coconut.itertools.takewhile, _coconut.itertools.dropwhile ''' else: raise CoconutInternalException("invalid header type", which) diff --git a/tests/src/cocotest/agnostic/main.coco b/tests/src/cocotest/agnostic/main.coco index 58b4b7e77..9475d027e 100644 --- a/tests/src/cocotest/agnostic/main.coco +++ b/tests/src/cocotest/agnostic/main.coco @@ -315,6 +315,13 @@ def main_test(): assert reversed(x for x in range(10))[2:-3] |> tuple == range(3, 8) |> reversed |> tuple assert count(1, 2)[:3] |> tuple == (1, 3, 5) assert count(0.5, 0.5)[:3] |> tuple == (0.5, 1, 1.5) + assert [1, 2, 3] |> fmap$(x -> x+1) == [2, 3, 4] + assert (1, 2, 3) |> fmap$(x -> x+1) == (2, 3, 4) + assert "abc" |> fmap$(x -> x+"!") == "a!b!c!" + assert {1:"2", 2:"3"} |> fmap$(-> _+1) == {2:"2", 3:"3"} + assert {1, 2, 3} |> fmap$(-> _+1) == {2, 3, 4} + assert map((+)$(1), (1, 2, 3)) |> fmap$((*)$(2)) == map((*)$(2)..(+)$(1), (1, 2, 3)) + assert reversed((1, 2, 3)) |> fmap$((+)$(1)) == map((+)$(1), (1, 2, 3)) |> reversed return True def main(*args): diff --git a/tests/src/cocotest/agnostic/util.coco b/tests/src/cocotest/agnostic/util.coco index dd7c5df76..e7f36bdb9 100644 --- a/tests/src/cocotest/agnostic/util.coco +++ b/tests/src/cocotest/agnostic/util.coco @@ -200,7 +200,7 @@ def is_null(item): return False data Tuple(elems): def __new__(cls, *elems) = - elems |> datamaker$(cls) + elems |> datamaker(cls) # Factorial: def factorial1(value): From ba1b9efa8790273fc95a88b13cf2dbfc4b15154d Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Wed, 15 Mar 2017 23:14:03 -0700 Subject: [PATCH 072/176] Further improves fmap --- coconut/compiler/header.py | 8 ++++---- tests/src/cocotest/agnostic/main.coco | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/coconut/compiler/header.py b/coconut/compiler/header.py index a51e93aea..588ddd772 100644 --- a/coconut/compiler/header.py +++ b/coconut/compiler/header.py @@ -341,7 +341,7 @@ def __reduce_ex__(self, _): def __copy__(self): return self.__class__(self._func, _coconut.copy.copy(self._iter)) def __fmap__(self, func): - raise _coconut.TypeError("fmap not supported for filter objects") + return _coconut_map(func, self) class zip(_coconut.zip): __slots__ = ("_iters",) if hasattr(_coconut.zip, "__doc__"): @@ -368,7 +368,7 @@ def __reduce_ex__(self, _): def __copy__(self): return self.__class__(*_coconut_map(_coconut.copy.copy, self._iters)) def __fmap__(self, func): - raise _coconut.TypeError("fmap not supported for zip objects") + return _coconut_map(func, self) class enumerate(_coconut.enumerate): __slots__ = ("_iter", "_start") if hasattr(_coconut.enumerate, "__doc__"): @@ -393,7 +393,7 @@ def __reduce_ex__(self, _): def __copy__(self): return self.__class__(_coconut.copy.copy(self._iter), self._start) def __fmap__(self, func): - raise _coconut.TypeError("fmap not supported for enumerate objects")''' + return _coconut_map(func, self)''' if target.startswith("3"): header += r''' class count:''' @@ -442,7 +442,7 @@ def __copy__(self): def __eq__(self, other): return isinstance(other, self.__class__) and self._start == other._start and self._step == other._step def __fmap__(self, func): - raise _coconut.TypeError("fmap not supported for count objects") + return _coconut_map(func, self) def recursive_iterator(func): """Decorates a function by optimizing it for iterator recursion. Requires function arguments to be pickleable.""" diff --git a/tests/src/cocotest/agnostic/main.coco b/tests/src/cocotest/agnostic/main.coco index 9475d027e..fc9f94e03 100644 --- a/tests/src/cocotest/agnostic/main.coco +++ b/tests/src/cocotest/agnostic/main.coco @@ -320,8 +320,8 @@ def main_test(): assert "abc" |> fmap$(x -> x+"!") == "a!b!c!" assert {1:"2", 2:"3"} |> fmap$(-> _+1) == {2:"2", 3:"3"} assert {1, 2, 3} |> fmap$(-> _+1) == {2, 3, 4} - assert map((+)$(1), (1, 2, 3)) |> fmap$((*)$(2)) == map((*)$(2)..(+)$(1), (1, 2, 3)) - assert reversed((1, 2, 3)) |> fmap$((+)$(1)) == map((+)$(1), (1, 2, 3)) |> reversed + assert map((+)$(1), (1, 2, 3)) |> fmap$((*)$(2)) |> repr == map((*)$(2)..(+)$(1), (1, 2, 3)) |> repr + assert reversed((1, 2, 3)) |> fmap$((+)$(1)) |> repr == map((+)$(1), (1, 2, 3)) |> reversed |> repr return True def main(*args): From 594ebd750395720b172d018d0bb9f0c3fe0dfea2 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Thu, 16 Mar 2017 00:56:41 -0700 Subject: [PATCH 073/176] Fixes fmap implementation --- coconut/compiler/header.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/coconut/compiler/header.py b/coconut/compiler/header.py index 588ddd772..53448c3f8 100644 --- a/coconut/compiler/header.py +++ b/coconut/compiler/header.py @@ -531,16 +531,15 @@ def fmap(func, obj): args = _coconut_map(func, obj) if _coconut.isinstance(obj, _coconut.map): return args - elif _coconut.isinstance(obj, _coconut.str): + if _coconut.isinstance(obj, _coconut.str): return "".join(args) - elif _coconut.isinstance(obj, _coconut.dict): + if _coconut.isinstance(obj, _coconut.dict): args = _coconut_zip(args, obj.values()) - elif not _coconut.isinstance(obj, (_coconut.tuple, _coconut.list, _coconut.set, _coconut.frozenset)): - try: - return obj.__class__(*args) - except _coconut.TypeError: - pass - return obj.__class__(args) + args, _args = _coconut_tee(args) + try: + return obj.__class__(*_args) + except _coconut.TypeError: + return obj.__class__(args) _coconut_MatchError, _coconut_count, _coconut_enumerate, _coconut_reversed, _coconut_map, _coconut_tee, _coconut_zip, reduce, takewhile, dropwhile = MatchError, count, enumerate, reversed, map, tee, zip, _coconut.functools.reduce, _coconut.itertools.takewhile, _coconut.itertools.dropwhile ''' else: From 13d5e40e840cf1d03059713a49990459eb93b45b Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Thu, 16 Mar 2017 01:38:39 -0700 Subject: [PATCH 074/176] Fixes dictionary fmap --- coconut/compiler/header.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/coconut/compiler/header.py b/coconut/compiler/header.py index 53448c3f8..643a7d846 100644 --- a/coconut/compiler/header.py +++ b/coconut/compiler/header.py @@ -531,15 +531,17 @@ def fmap(func, obj): args = _coconut_map(func, obj) if _coconut.isinstance(obj, _coconut.map): return args - if _coconut.isinstance(obj, _coconut.str): + elif _coconut.isinstance(obj, _coconut.str): return "".join(args) - if _coconut.isinstance(obj, _coconut.dict): + elif _coconut.isinstance(obj, _coconut.dict): args = _coconut_zip(args, obj.values()) - args, _args = _coconut_tee(args) - try: - return obj.__class__(*_args) - except _coconut.TypeError: - return obj.__class__(args) + else: + args, _args = _coconut_tee(args) + try: + return obj.__class__(*_args) + except _coconut.TypeError: + pass + return obj.__class__(args) _coconut_MatchError, _coconut_count, _coconut_enumerate, _coconut_reversed, _coconut_map, _coconut_tee, _coconut_zip, reduce, takewhile, dropwhile = MatchError, count, enumerate, reversed, map, tee, zip, _coconut.functools.reduce, _coconut.itertools.takewhile, _coconut.itertools.dropwhile ''' else: From 880beac5791762df98d8dfa2b25df6b12c14e15f Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Thu, 16 Mar 2017 02:03:42 -0700 Subject: [PATCH 075/176] Standardizes fmap behaviour --- DOCS.md | 14 +------------- coconut/compiler/header.py | 16 ++++++---------- tests/src/cocotest/agnostic/main.coco | 1 + 3 files changed, 8 insertions(+), 23 deletions(-) diff --git a/DOCS.md b/DOCS.md index cf16df7da..c795751d0 100644 --- a/DOCS.md +++ b/DOCS.md @@ -1587,19 +1587,7 @@ class Tuple(collections.namedtuple("Tuple", "elems")): In functional programming, `fmap(func, obj)` takes a data type `obj` and returns a new data type with `func` mapped over the contents. Coconut's `fmap` function does the exact same thing in Coconut. -For non-built-ins, equivalent to: -```coconut -def fmap(func, obj): - """Creates a copy of obj with func applied to its contents.""" - if hasattr(obj, "__fmap__"): - return obj.__fmap__(func) - else: - args = obj |> map$(func) - try: - return obj.__class__(*args) - except TypeError: - return obj.__class__(args) -``` +`fmap` can also be used on built-ins such as `str`, `list`, `set`, and `dict` as a variant of `map` that returns back an object of the same type. The behavior of `fmap` for a given object can be overridden by defining an `__fmap__(self, func)` method that will be called whenever `fmap` is invoked on that object. ##### Example diff --git a/coconut/compiler/header.py b/coconut/compiler/header.py index 643a7d846..20e17eac3 100644 --- a/coconut/compiler/header.py +++ b/coconut/compiler/header.py @@ -529,18 +529,14 @@ def fmap(func, obj): if _coconut.hasattr(obj, "__fmap__"): return obj.__fmap__(func) args = _coconut_map(func, obj) - if _coconut.isinstance(obj, _coconut.map): - return args - elif _coconut.isinstance(obj, _coconut.str): + if _coconut.isinstance(obj, _coconut.str): return "".join(args) - elif _coconut.isinstance(obj, _coconut.dict): + if _coconut.isinstance(obj, _coconut.dict): args = _coconut_zip(args, obj.values()) - else: - args, _args = _coconut_tee(args) - try: - return obj.__class__(*_args) - except _coconut.TypeError: - pass + if _coconut.isinstance(obj, _coconut.tuple) and _coconut.hasattr(obj, "_make"): + return obj._make(args) + if _coconut.isinstance(obj, _coconut.map): + return args return obj.__class__(args) _coconut_MatchError, _coconut_count, _coconut_enumerate, _coconut_reversed, _coconut_map, _coconut_tee, _coconut_zip, reduce, takewhile, dropwhile = MatchError, count, enumerate, reversed, map, tee, zip, _coconut.functools.reduce, _coconut.itertools.takewhile, _coconut.itertools.dropwhile ''' diff --git a/tests/src/cocotest/agnostic/main.coco b/tests/src/cocotest/agnostic/main.coco index fc9f94e03..a78b09ad6 100644 --- a/tests/src/cocotest/agnostic/main.coco +++ b/tests/src/cocotest/agnostic/main.coco @@ -322,6 +322,7 @@ def main_test(): assert {1, 2, 3} |> fmap$(-> _+1) == {2, 3, 4} assert map((+)$(1), (1, 2, 3)) |> fmap$((*)$(2)) |> repr == map((*)$(2)..(+)$(1), (1, 2, 3)) |> repr assert reversed((1, 2, 3)) |> fmap$((+)$(1)) |> repr == map((+)$(1), (1, 2, 3)) |> reversed |> repr + assert [[1, 2, 3]] |> fmap$((+)$([0])) == [[0, 1, 2, 3]] return True def main(*args): From c7e8297ab382d32fce803b43f92c28dffa66be8b Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Thu, 16 Mar 2017 02:17:51 -0700 Subject: [PATCH 076/176] Fixes an fmap test --- coconut/root.py | 2 +- tests/src/cocotest/agnostic/main.coco | 2 -- tests/src/cocotest/agnostic/suite.coco | 2 ++ tests/src/cocotest/agnostic/util.coco | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/coconut/root.py b/coconut/root.py index e93a6944d..ed9ee82e2 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -26,7 +26,7 @@ VERSION = "1.2.2" VERSION_NAME = "Colonel" # False for release, int >= 1 for develop -DEVELOP = 5 +DEVELOP = 6 #----------------------------------------------------------------------------------------------------------------------- # CONSTANTS: diff --git a/tests/src/cocotest/agnostic/main.coco b/tests/src/cocotest/agnostic/main.coco index a78b09ad6..5da41784a 100644 --- a/tests/src/cocotest/agnostic/main.coco +++ b/tests/src/cocotest/agnostic/main.coco @@ -320,8 +320,6 @@ def main_test(): assert "abc" |> fmap$(x -> x+"!") == "a!b!c!" assert {1:"2", 2:"3"} |> fmap$(-> _+1) == {2:"2", 3:"3"} assert {1, 2, 3} |> fmap$(-> _+1) == {2, 3, 4} - assert map((+)$(1), (1, 2, 3)) |> fmap$((*)$(2)) |> repr == map((*)$(2)..(+)$(1), (1, 2, 3)) |> repr - assert reversed((1, 2, 3)) |> fmap$((+)$(1)) |> repr == map((+)$(1), (1, 2, 3)) |> reversed |> repr assert [[1, 2, 3]] |> fmap$((+)$([0])) == [[0, 1, 2, 3]] return True diff --git a/tests/src/cocotest/agnostic/suite.coco b/tests/src/cocotest/agnostic/suite.coco index 27a33db00..5df79347f 100644 --- a/tests/src/cocotest/agnostic/suite.coco +++ b/tests/src/cocotest/agnostic/suite.coco @@ -286,4 +286,6 @@ def suite_test(): assert Just(3) |> map$(-> _*2) |*> Just == Just(6) == Just(3) |> fmap$(-> _*2) assert Nothing() |> map$(-> _*2) |*> Nothing == Nothing() == Nothing() |> fmap$(-> _*2) assert Tuple(1, 2, 3) != Tuple(1, 2) + assert map(plus1, (1, 2, 3)) |> fmap$(times2) |> repr == map(times2..plus1, (1, 2, 3)) |> repr + assert reversed((1, 2, 3)) |> fmap$(plus1) |> repr == map(plus1, (1, 2, 3)) |> reversed |> repr return True diff --git a/tests/src/cocotest/agnostic/util.coco b/tests/src/cocotest/agnostic/util.coco index e7f36bdb9..9b66f39fe 100644 --- a/tests/src/cocotest/agnostic/util.coco +++ b/tests/src/cocotest/agnostic/util.coco @@ -40,6 +40,7 @@ def chain2(a, b): yield from a yield from b def threeple(a, b, c) = (a, b, c) +times2 = (*)$(2) # Partial Applications: sum_ = reduce$((+)) From e877c146426073e869f137849abf0e53823628df Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Thu, 16 Mar 2017 23:38:31 -0700 Subject: [PATCH 077/176] Fixes handling of MyPy errors --- coconut/command/mypy.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/coconut/command/mypy.py b/coconut/command/mypy.py index a033235ba..6e60196b3 100644 --- a/coconut/command/mypy.py +++ b/coconut/command/mypy.py @@ -22,6 +22,7 @@ import traceback from coconut.exceptions import CoconutException +from coconut.terminal import printerr try: from mypy.api import run @@ -39,8 +40,9 @@ def mypy_run(args): try: stdout, stderr, exit_code = run(args) except BaseException: - return traceback.format_exc() - for line in stdout.splitlines(): - yield line, False - for line in stderr.splitlines(): - yield line, True + printerr(traceback.format_exc()) + else: + for line in stdout.splitlines(): + yield line, False + for line in stderr.splitlines(): + yield line, True From 487da5d2aa2b9402653a9883f01900dcbcab855b Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Fri, 17 Mar 2017 01:22:03 -0700 Subject: [PATCH 078/176] Bumps develop version --- coconut/root.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coconut/root.py b/coconut/root.py index ed9ee82e2..16021258f 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -26,7 +26,7 @@ VERSION = "1.2.2" VERSION_NAME = "Colonel" # False for release, int >= 1 for develop -DEVELOP = 6 +DEVELOP = 7 #----------------------------------------------------------------------------------------------------------------------- # CONSTANTS: From b6d1192c054764a033375bc3f0810c363c0d6c61 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Fri, 17 Mar 2017 13:08:11 -0700 Subject: [PATCH 079/176] Fixes fmap of range --- coconut/compiler/header.py | 6 +++--- tests/src/cocotest/agnostic/main.coco | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/coconut/compiler/header.py b/coconut/compiler/header.py index 20e17eac3..eba6e45b3 100644 --- a/coconut/compiler/header.py +++ b/coconut/compiler/header.py @@ -529,14 +529,14 @@ def fmap(func, obj): if _coconut.hasattr(obj, "__fmap__"): return obj.__fmap__(func) args = _coconut_map(func, obj) - if _coconut.isinstance(obj, _coconut.str): - return "".join(args) if _coconut.isinstance(obj, _coconut.dict): args = _coconut_zip(args, obj.values()) if _coconut.isinstance(obj, _coconut.tuple) and _coconut.hasattr(obj, "_make"): return obj._make(args) - if _coconut.isinstance(obj, _coconut.map): + if _coconut.isinstance(obj, (_coconut.map, _coconut.range)): return args + if _coconut.isinstance(obj, _coconut.str): + return "".join(args) return obj.__class__(args) _coconut_MatchError, _coconut_count, _coconut_enumerate, _coconut_reversed, _coconut_map, _coconut_tee, _coconut_zip, reduce, takewhile, dropwhile = MatchError, count, enumerate, reversed, map, tee, zip, _coconut.functools.reduce, _coconut.itertools.takewhile, _coconut.itertools.dropwhile ''' diff --git a/tests/src/cocotest/agnostic/main.coco b/tests/src/cocotest/agnostic/main.coco index 5da41784a..362322638 100644 --- a/tests/src/cocotest/agnostic/main.coco +++ b/tests/src/cocotest/agnostic/main.coco @@ -321,6 +321,7 @@ def main_test(): assert {1:"2", 2:"3"} |> fmap$(-> _+1) == {2:"2", 3:"3"} assert {1, 2, 3} |> fmap$(-> _+1) == {2, 3, 4} assert [[1, 2, 3]] |> fmap$((+)$([0])) == [[0, 1, 2, 3]] + assert range(3) |> fmap$(-> _ + 1) |> tuple == (0, 1, 2) return True def main(*args): From ee989b96eb423570e54100ab1ad0fe1e60c31666 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Mon, 20 Mar 2017 16:49:00 -0700 Subject: [PATCH 080/176] Removes table of contents from readme --- .gitignore | 1 + Makefile | 4 ++-- README.rst | 8 -------- coconut/constants.py | 20 ++++++++++++++++++++ conf.py | 15 ++++++++++++++- setup.py | 15 +-------------- 6 files changed, 38 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index bb97c6904..b4280fa42 100644 --- a/.gitignore +++ b/.gitignore @@ -73,3 +73,4 @@ target/ # Coconut tests/dest/ docs/ +index.rst diff --git a/Makefile b/Makefile index 39539f753..6009f6fa2 100644 --- a/Makefile +++ b/Makefile @@ -21,12 +21,12 @@ test: .PHONY: docs docs: clean sphinx-build -b html . ./docs - cp ./docs/README.html ./docs/index.html pushd ./docs; zip -r ./docs.zip ./*; popd + rm -rf index.rst .PHONY: clean clean: - rm -rf ./docs ./dist ./build ./tests/dest + rm -rf ./docs ./dist ./build ./tests/dest index.rst find . -name '*.pyc' -delete find . -name '__pycache__' -delete diff --git a/README.rst b/README.rst index 5dab62e96..431981856 100644 --- a/README.rst +++ b/README.rst @@ -1,14 +1,6 @@ Coconut ======= -.. toctree:: - :maxdepth: 3 - - FAQ - HELP - DOCS - CONTRIBUTING - Coconut (`coconut-lang.org`__) is a variant of Python_ built for **simple, elegant, Pythonic functional programming**. Coconut is developed on GitHub_ and hosted on PyPI_. Installing Coconut is as easy as opening a command prompt and entering:: diff --git a/coconut/constants.py b/coconut/constants.py index bb6e89091..f2e6409a0 100644 --- a/coconut/constants.py +++ b/coconut/constants.py @@ -478,3 +478,23 @@ def fixpath(path): mimetype = "text/x-python3" all_keywords = keywords + const_vars + reserved_vars + +#----------------------------------------------------------------------------------------------------------------------- +# DOCUMENTATION CONSTANTS: +#----------------------------------------------------------------------------------------------------------------------- + +without_toc = """ +======= +""" + +with_toc = """ +======= + +.. toctree:: + :maxdepth: 3 + + FAQ + HELP + DOCS + CONTRIBUTING +""" diff --git a/conf.py b/conf.py index a9640e09d..15c92ce96 100644 --- a/conf.py +++ b/conf.py @@ -23,9 +23,21 @@ from coconut.root import * # NOQA +from coconut.constants import without_toc, with_toc + from recommonmark.parser import CommonMarkParser from sphinx_bootstrap_theme import get_html_theme_path +#----------------------------------------------------------------------------------------------------------------------- +# README: +#----------------------------------------------------------------------------------------------------------------------- + +with open("README.rst", "r") as readme_file: + readme = readme_file.read() + +with open("index.rst", "w") as index_file: + index_file.write(readme.replace(without_toc, with_toc)) + #----------------------------------------------------------------------------------------------------------------------- # DEFINITIONS: #----------------------------------------------------------------------------------------------------------------------- @@ -41,8 +53,9 @@ version = VERSION release = VERSION_STR_TAG -master_doc = "README" +master_doc = "index" source_suffix = [".rst", ".md"] source_parsers = { ".md": CommonMarkParser } +exclude_patterns = ["README.*"] diff --git a/setup.py b/setup.py index 5fc53c08b..46ecf7328 100644 --- a/setup.py +++ b/setup.py @@ -33,20 +33,7 @@ #----------------------------------------------------------------------------------------------------------------------- with open("README.rst", "r") as readme_file: - readme_lines = [] - in_toc = False - for line in readme_file.readlines(): - if line.startswith(".. toctree::"): - in_toc = True - elif in_toc and 0 < len(line.lstrip()) == len(line): - in_toc = False - if not in_toc: - readme_lines.append(line) - readme = "".join(readme_lines) - -#----------------------------------------------------------------------------------------------------------------------- -# MAIN: -#----------------------------------------------------------------------------------------------------------------------- + readme = readme_file.read() setuptools.setup( name="coconut" + ("-develop" if DEVELOP else ""), From 28292f7302ff81f4c013c6ba85a81b706d333ad7 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Mon, 20 Mar 2017 22:44:15 -0700 Subject: [PATCH 081/176] Fixes broken test --- tests/src/cocotest/agnostic/main.coco | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/cocotest/agnostic/main.coco b/tests/src/cocotest/agnostic/main.coco index 362322638..ef9d6318e 100644 --- a/tests/src/cocotest/agnostic/main.coco +++ b/tests/src/cocotest/agnostic/main.coco @@ -321,7 +321,7 @@ def main_test(): assert {1:"2", 2:"3"} |> fmap$(-> _+1) == {2:"2", 3:"3"} assert {1, 2, 3} |> fmap$(-> _+1) == {2, 3, 4} assert [[1, 2, 3]] |> fmap$((+)$([0])) == [[0, 1, 2, 3]] - assert range(3) |> fmap$(-> _ + 1) |> tuple == (0, 1, 2) + assert range(3) |> fmap$(-> _ + 1) |> tuple == (1, 2, 3) return True def main(*args): From 290f8cef7b7fcc12957e8b21690ac39342409c06 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 21 Mar 2017 12:58:04 -0700 Subject: [PATCH 082/176] Fixes minor documentation issues --- DOCS.md | 10 +++++----- coconut/command/cli.py | 3 ++- coconut/constants.py | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/DOCS.md b/DOCS.md index c795751d0..83ad7530b 100644 --- a/DOCS.md +++ b/DOCS.md @@ -203,11 +203,11 @@ By default, if the `source` argument to the command-line utility is a file, it w While Coconut syntax is based off of Python 3, Coconut code compiled in universal mode (the default `--target`), and the Coconut compiler, should run on any Python version `>= 2.6` on the `2.x` branch or `>= 3.2` on the `3.x` branch. -_Note: The tested against implementations are [CPython](https://www.python.org/) `2.6, 2.7, 3.2, 3.3, 3.4, 3.5` and [PyPy](http://pypy.org/) `2.7, 3.2`._ +_Note: The tested against implementations are [CPython](https://www.python.org/) `2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6` and [PyPy](http://pypy.org/) `2.7, 3.2`._ As part of Coconut's cross-compatibility efforts, Coconut adds in new Python 3 built-ins and overwrites Python 2 built-ins to use the Python 3 versions where possible. Additionally, Coconut also overrides some Python 3 built-ins for optimization purposes. If access to the Python versions is desired, the old built-ins can be retrieved by prefixing them with `py_`. -Finally, while Coconut will try to compile Python-3-specific syntax to its universal equivalent, the follow constructs have no equivalent in Python 2, and require a target of at least `3` to be specified to be used: +Finally, while Coconut will try to compile Python-3-specific syntax to its universal equivalent, the following constructs have no equivalent in Python 2, and require the specification of a target of at least `3` to be used: - destructuring assignment with `*`s (use Coconut pattern-matching instead), - the `nonlocal` keyword, - `exec` used in a context where it must be a function, @@ -215,11 +215,11 @@ Finally, while Coconut will try to compile Python-3-specific syntax to its unive - tuples and lists with `*` unpacking or dicts with `**` unpacking (requires `--target 3.5`), - `@` as matrix multiplication (requires `--target 3.5`), - `async` and `await` statements (requires `--target 3.5`), and -- formatting `f` strings (requires `--target 3.6`). +- formatting strings by prefixing them with `f` (requires `--target 3.6`). ### Allowable Targets -If the version of Python that the compiled code will be running on is known ahead of time, a target should be specified with `--target`. The given target will only affect the compiled code and whether or not certain Python-3-specific syntax is allowed, detailed below. Where Python 3 and Python 2 syntax standards differ, Coconut syntax will always follow Python 3 across all targets. The supported targets are: +If the version of Python that the compiled code will be running on is known ahead of time, a target should be specified with `--target`. The given target will only affect the compiled code and whether or not the Python-3-specific syntax detailed above is allowed. Where Python 3 and Python 2 syntax standards differ, Coconut syntax will always follow Python 3 across all targets. The supported targets are: - universal (default) (will work on _any_ of the below), - `2`, `26` (will work on any Python `>= 2.6` but `< 3`), @@ -254,7 +254,7 @@ If you prefer [IPython](http://ipython.org/) (the python kernel for the [Jupyter If Coconut is used as a kernel, all code in the console or notebook will be sent directly to Coconut instead of Python to be evaluated. Otherwise, the Coconut kernel behaves exactly like the IPython kernel, including support for `%magic` commands. -The command `coconut --jupyter notebook` (or `coconut --ipython notebook`) will launch an IPython/ Jupyter notebook using Coconut as the kernel and the command `coconut --jupyter console` (or `coconut --ipython console`) will launch an IPython/ Jupyter console using Coconut as the kernel. Additionally, the command `coconut --jupyter` (or `coconut --ipython`) will add Coconut as a language option inside of all IPython/ Jupyter notebooks, even those not launched with Coconut. This command may need to be re-run when a new version of Coconut is installed. +The command `coconut --jupyter notebook` (or `coconut --ipython notebook`) will launch an IPython/Jupyter notebook using Coconut as the kernel and the command `coconut --jupyter console` (or `coconut --ipython console`) will launch an IPython/Jupyter console using Coconut as the kernel. Additionally, the command `coconut --jupyter` (or `coconut --ipython`) will add Coconut as a language option inside of all IPython/Jupyter notebooks, even those not launched with Coconut. This command may need to be re-run when a new version of Coconut is installed. #### Extension diff --git a/coconut/command/cli.py b/coconut/command/cli.py index d9b6ba5fa..b69b22487 100644 --- a/coconut/command/cli.py +++ b/coconut/command/cli.py @@ -27,6 +27,7 @@ default_recursion_limit, style_env_var, default_style, + main_sig, ) #----------------------------------------------------------------------------------------------------------------------- @@ -54,7 +55,7 @@ arguments.add_argument( "-v", "--version", action="version", - version=version_long, + version=main_sig + version_long, help="print Coconut and Python version information") arguments.add_argument( diff --git a/coconut/constants.py b/coconut/constants.py index f2e6409a0..3ca516c68 100644 --- a/coconut/constants.py +++ b/coconut/constants.py @@ -377,7 +377,7 @@ def fixpath(path): info_tabulation = 18 # offset for tabulated info messages -version_long = main_sig + "Version " + VERSION_STR + " running on Python " + sys.version.split()[0] +version_long = "Version " + VERSION_STR + " running on Python " + sys.version.split()[0] version_banner = "Coconut " + VERSION_STR if DEVELOP: version_tag = "develop" From 113d8270547b1a41e26954d8831190afefb20e6d Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 21 Mar 2017 13:30:49 -0700 Subject: [PATCH 083/176] Documents dotted function definition --- DOCS.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/DOCS.md b/DOCS.md index 83ad7530b..d0fd4e8f0 100644 --- a/DOCS.md +++ b/DOCS.md @@ -39,6 +39,7 @@ 1. [Set Literals](#set-literals) 1. [Imaginary Literals](#imaginary-literals) 1. [Underscore Separators](#underscore-separators) +1. [Dotted Function Definition](#dotted-function-definition) 1. [Function Definition](#function-definition) 1. [Tail Call Optimization](#tail-call-optimization) 1. [Operator Functions](#operator-functions) @@ -929,7 +930,7 @@ print(abs(3 + 4j)) ### Underscore Separators -Coconut allows for one underscore between digits and after base specifiers in numeric literals. These underscores are ignored and should only be used to increase code readability. +Coconut allows for one underscore between digits and after base specifiers in numeric literals as specified in [PEP 515](https://www.python.org/dev/peps/pep-0515/). These underscores are ignored and should only be used to increase code readability. ##### Example @@ -943,6 +944,25 @@ Coconut allows for one underscore between digits and after base specifiers in nu 10000000.0 ``` +## Dotted Function Definition + +Coconut allows for function definition using a dotted name to assign a function as a method of an object as specified in [PEP 542](https://www.python.org/dev/peps/pep-0542/). + +##### Example + +###### Coconut +```coconut +def MyClass.my_method(self): + ... +``` + +###### Python +```coconut_python +def my_method(self): + ... +MyClass.my_method = my_method +``` + ## Function Definition ### Tail Call Optimization From 87cef886ceca57a821c956373cf15ab23775c23f Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 21 Mar 2017 14:00:24 -0700 Subject: [PATCH 084/176] Fixes minor tutorial errors --- HELP.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/HELP.md b/HELP.md index 53e4578fb..364e7f42c 100644 --- a/HELP.md +++ b/HELP.md @@ -65,7 +65,7 @@ and much more! At its very core, Coconut is a compiler that turns Coconut code into Python code. That means that anywhere where you can use a Python script, you can also use a compiled Coconut script. To access that core compiler, Coconut comes with a command-line utility, which can - compile single Coconut files or entire Coconut projects, - interpret Coconut code on-the-fly, and -- hook into existing Python applications like IPython/ Jupyter. +- hook into existing Python applications like IPython/Jupyter and MyPy. Installing Coconut, including all the features above, is drop-dead simple. Just @@ -84,7 +84,7 @@ coconut -h ``` which should display Coconut's command-line help. -_Note: If you're having trouble installing Coconut, or if anything else mentioned in this tutorial doesn't seem to work for you, feel free to [open an issue](https://github.com/evhub/coconut/issues/new) and it'll be addressed as soon as possible._ +_Note: If you're having trouble installing Coconut, or if anything else mentioned in this tutorial doesn't seem to work for you, feel free to [ask for help on Gitter](https://gitter.im/evhub/coconut) and somebody will try to answer your question as soon as possible._ ## Starting Out @@ -160,7 +160,7 @@ The Coconut compiler supports a large variety of different compilation options, Although all different types of programming can benefit from using more functional techniques, scientific computing, perhaps more than any other field, lends itself very well to functional programming, an observation the case studies in this tutorial are very good examples of. To that end, Coconut aims to provide extensive support for the established tools of scientific computing in Python. -That means supporting IPython/ Jupyter, as modern Python programming, particularly in the sciences, has gravitated towards the use of [IPython](http://ipython.org/) (the python kernel for the [Jupyter](http://jupyter.org/) framework) instead of the classic Python shell. Coconut supports being used as a kernel for Jupyter notebooks and consoles, allowing Coconut code to be used alongside powerful IPython features such as `%magic` commands. +That means supporting IPython/Jupyter, as modern Python programming, particularly in the sciences, has gravitated towards the use of [IPython](http://ipython.org/) (the python kernel for the [Jupyter](http://jupyter.org/) framework) instead of the classic Python shell. Coconut supports being used as a kernel for Jupyter notebooks and consoles, allowing Coconut code to be used alongside powerful IPython features such as `%magic` commands. To launch a Jupyter notebook with Coconut as the kernel, use the command ``` @@ -204,7 +204,7 @@ def factorial(n): 0 |> factorial |> print # 1 3 |> factorial |> print # 6 ``` -Before we delve into what exactly is happening here, let's give it a run and make sure the test cases check out. If we were really writing a Coconut program, we'd want to save and compile an actual file, but since we're just playing around, let's try copy-pasting into the interpreter. Here, you should get `1`, `6`, and then two `TypeError`s. +Before we delve into what exactly is happening here, let's give it a run and make sure the test cases check out. If we were really writing a Coconut program, we'd want to save and compile an actual file, but since we're just playing around, let's try copy-pasting into the interpreter. Here, you should get two `TypeErrors`, then `1`, then `6`. Now that we've verified it works, let's take a look at what's going on. Since the imperative approach is a fundamentally non-functional method, Coconut can't help us improve this example very much. Even here, though, the use of Coconut's infix notation (where the function is put in-between its arguments, surrounded in backticks) in `` n `isinstance` int `` makes the code slightly cleaner and easier to read. From 406cc314e1103aca8c5e2442ad87d12157cfcaa5 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 21 Mar 2017 16:25:40 -0700 Subject: [PATCH 085/176] Fix TCO of function call of function call Fixes #235. --- coconut/compiler/grammar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coconut/compiler/grammar.py b/coconut/compiler/grammar.py index 5f0b2ea3f..b06cf989b 100644 --- a/coconut/compiler/grammar.py +++ b/coconut/compiler/grammar.py @@ -1211,7 +1211,7 @@ class Grammar(object): tco_return = attach( Keyword("return").suppress() + condense( (base_name | parens | brackets | braces | string) - + ZeroOrMore(dot + base_name | brackets) + + ZeroOrMore(dot + base_name | brackets | parens + ~end_marker) ) + parens + end_marker, tco_return_handle) rest_of_arg = ZeroOrMore(parens | brackets | braces | ~comma + ~rparen + any_char) From 5fc67b331624183394d28377d840b82e57c45ae8 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 21 Mar 2017 16:37:51 -0700 Subject: [PATCH 086/176] Adds multiple indexing implicit partial Refs #231. --- coconut/compiler/grammar.py | 3 ++- tests/src/cocotest/agnostic/suite.coco | 1 + tests/src/cocotest/agnostic/util.coco | 3 +++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/coconut/compiler/grammar.py b/coconut/compiler/grammar.py index b06cf989b..51e71bafc 100644 --- a/coconut/compiler/grammar.py +++ b/coconut/compiler/grammar.py @@ -715,6 +715,7 @@ class Grammar(object): slicetestgroup = Optional(test_no_chain, default="") sliceopgroup = unsafe_colon.suppress() + slicetestgroup subscriptgroup = attach(slicetestgroup + sliceopgroup + Optional(sliceopgroup) | test, subscriptgroup_handle) + subscriptgrouplist = itemlist(subscriptgroup, comma) testlist_comp = addspace((test | star_expr) + comp_for) | testlist_star_expr list_comp = condense(lbrack + Optional(testlist_comp) + rbrack) @@ -733,7 +734,7 @@ class Grammar(object): + Optional( lparen + Optional(methodcaller_args) + rparen.suppress() ), attr_handle) - itemgetter_atom = attach(dot.suppress() + condense(Optional(dollar) + lbrack) + subscriptgroup + rbrack.suppress(), itemgetter_handle) + itemgetter_atom = attach(dot.suppress() + condense(Optional(dollar) + lbrack) + subscriptgrouplist + rbrack.suppress(), itemgetter_handle) set_literal = Forward() set_letter_literal = Forward() set_s = fixto(CaselessLiteral("s"), "s") diff --git a/tests/src/cocotest/agnostic/suite.coco b/tests/src/cocotest/agnostic/suite.coco index 5df79347f..6481b23f5 100644 --- a/tests/src/cocotest/agnostic/suite.coco +++ b/tests/src/cocotest/agnostic/suite.coco @@ -288,4 +288,5 @@ def suite_test(): assert Tuple(1, 2, 3) != Tuple(1, 2) assert map(plus1, (1, 2, 3)) |> fmap$(times2) |> repr == map(times2..plus1, (1, 2, 3)) |> repr assert reversed((1, 2, 3)) |> fmap$(plus1) |> repr == map(plus1, (1, 2, 3)) |> reversed |> repr + assert identity_getitem()[1:2, 2:3] == (slice(1, 2), slice(2, 3)) == identity_getitem() |> .[1:2, 2:3] return True diff --git a/tests/src/cocotest/agnostic/util.coco b/tests/src/cocotest/agnostic/util.coco index 9b66f39fe..98e02677c 100644 --- a/tests/src/cocotest/agnostic/util.coco +++ b/tests/src/cocotest/agnostic/util.coco @@ -575,6 +575,9 @@ def ret_none(n): def ret_args_kwargs(*args, **kwargs) = (args, kwargs) +class identity_getitem: + def __getitem__(self, *args) = args + # Typing import sys From 236ac4eab57bbfde556d388db4134dc5da16b49d Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 21 Mar 2017 16:50:38 -0700 Subject: [PATCH 087/176] Adds stargs and kwargs support to methodcaller partial Refs #228. --- coconut/compiler/grammar.py | 2 +- tests/src/cocotest/agnostic/suite.coco | 3 ++- tests/src/cocotest/agnostic/util.coco | 4 +++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/coconut/compiler/grammar.py b/coconut/compiler/grammar.py index 51e71bafc..06f373bb3 100644 --- a/coconut/compiler/grammar.py +++ b/coconut/compiler/grammar.py @@ -703,7 +703,7 @@ class Grammar(object): questionmark | dubstar + test | star + test | name + default | test ), comma)) methodcaller_args = ( - itemlist(condense(name + default | test), comma) + itemlist(condense(dubstar + test | star + test | name + default | test), comma) | op_item ) diff --git a/tests/src/cocotest/agnostic/suite.coco b/tests/src/cocotest/agnostic/suite.coco index 6481b23f5..fab448f87 100644 --- a/tests/src/cocotest/agnostic/suite.coco +++ b/tests/src/cocotest/agnostic/suite.coco @@ -288,5 +288,6 @@ def suite_test(): assert Tuple(1, 2, 3) != Tuple(1, 2) assert map(plus1, (1, 2, 3)) |> fmap$(times2) |> repr == map(times2..plus1, (1, 2, 3)) |> repr assert reversed((1, 2, 3)) |> fmap$(plus1) |> repr == map(plus1, (1, 2, 3)) |> reversed |> repr - assert identity_getitem()[1:2, 2:3] == (slice(1, 2), slice(2, 3)) == identity_getitem() |> .[1:2, 2:3] + assert ident[1:2, 2:3] == (slice(1, 2), slice(2, 3)) == ident |> .[1:2, 2:3] + assert ident.method(*(1,), **{"a": 2}) == ((1,), {"a": 1}) == ident |> .method(*(1,), **{"a": 2}) return True diff --git a/tests/src/cocotest/agnostic/util.coco b/tests/src/cocotest/agnostic/util.coco index 98e02677c..d3b67726a 100644 --- a/tests/src/cocotest/agnostic/util.coco +++ b/tests/src/cocotest/agnostic/util.coco @@ -575,8 +575,10 @@ def ret_none(n): def ret_args_kwargs(*args, **kwargs) = (args, kwargs) -class identity_getitem: +class identity_operations: def __getitem__(self, *args) = args + def method(self, *args, **kwargs) = (args, kwargs) +ident = identity_operations() # Typing From e07e72339beb10137d4cd3d522b961cacdc26021 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 21 Mar 2017 17:44:37 -0700 Subject: [PATCH 088/176] Fixes multiple getitem test --- tests/src/cocotest/agnostic/util.coco | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/cocotest/agnostic/util.coco b/tests/src/cocotest/agnostic/util.coco index d3b67726a..552540c3f 100644 --- a/tests/src/cocotest/agnostic/util.coco +++ b/tests/src/cocotest/agnostic/util.coco @@ -576,7 +576,7 @@ def ret_none(n): def ret_args_kwargs(*args, **kwargs) = (args, kwargs) class identity_operations: - def __getitem__(self, *args) = args + def __getitem__(self, args) = args def method(self, *args, **kwargs) = (args, kwargs) ident = identity_operations() From 898af685d79c1a4a48d248a8351a87fb27f123a7 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 21 Mar 2017 18:06:53 -0700 Subject: [PATCH 089/176] Fixes an incorrect test --- tests/src/cocotest/agnostic/suite.coco | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/cocotest/agnostic/suite.coco b/tests/src/cocotest/agnostic/suite.coco index fab448f87..1c04310ce 100644 --- a/tests/src/cocotest/agnostic/suite.coco +++ b/tests/src/cocotest/agnostic/suite.coco @@ -289,5 +289,5 @@ def suite_test(): assert map(plus1, (1, 2, 3)) |> fmap$(times2) |> repr == map(times2..plus1, (1, 2, 3)) |> repr assert reversed((1, 2, 3)) |> fmap$(plus1) |> repr == map(plus1, (1, 2, 3)) |> reversed |> repr assert ident[1:2, 2:3] == (slice(1, 2), slice(2, 3)) == ident |> .[1:2, 2:3] - assert ident.method(*(1,), **{"a": 2}) == ((1,), {"a": 1}) == ident |> .method(*(1,), **{"a": 2}) + assert ident.method(*(1,), **{"a": 2}) == ((1,), {"a": 2}) == ident |> .method(*(1,), **{"a": 2}) return True From dbec508685a667cc2410f90b8ce3c26999dcf5ac Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 21 Mar 2017 18:10:12 -0700 Subject: [PATCH 090/176] Bumps develop version --- coconut/root.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coconut/root.py b/coconut/root.py index 16021258f..bc45126eb 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -26,7 +26,7 @@ VERSION = "1.2.2" VERSION_NAME = "Colonel" # False for release, int >= 1 for develop -DEVELOP = 7 +DEVELOP = 8 #----------------------------------------------------------------------------------------------------------------------- # CONSTANTS: From 308339f1fa7cbaee06be1ed0175926e5fb8ec3c8 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 21 Mar 2017 20:46:32 -0700 Subject: [PATCH 091/176] Universalizes object Refs #233. --- coconut/root.py | 13 +++++++++++-- tests/src/cocotest/agnostic/suite.coco | 6 ++++++ tests/src/cocotest/agnostic/util.coco | 8 ++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/coconut/root.py b/coconut/root.py index bc45126eb..c39388fdd 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -42,13 +42,22 @@ PY2 = _coconut_sys.version_info < (3,) PY26 = _coconut_sys.version_info < (2, 7) -PY3_HEADER = r'''py_chr, py_filter, py_hex, py_input, py_int, py_map, py_oct, py_open, py_print, py_range, py_str, py_zip, py_filter, py_reversed, py_enumerate = chr, filter, hex, input, int, map, oct, open, print, range, str, zip, filter, reversed, enumerate +PY3_HEADER = r'''py_chr, py_filter, py_hex, py_input, py_int, py_map, py_object, py_oct, py_open, py_print, py_range, py_str, py_zip, py_filter, py_reversed, py_enumerate = chr, filter, hex, input, int, map, object, oct, open, print, range, str, zip, filter, reversed, enumerate ''' PY27_HEADER = PY3_HEADER + r'''py_raw_input, py_xrange = raw_input, xrange -_coconut_raw_input, _coconut_xrange, _coconut_int, _coconut_long, _coconut_print, _coconut_str, _coconut_unicode, _coconut_repr = raw_input, xrange, int, long, print, str, unicode, repr +_coconut_NotImplemented, _coconut_raw_input, _coconut_xrange, _coconut_int, _coconut_long, _coconut_print, _coconut_str, _coconut_unicode, _coconut_repr = NotImplemented, raw_input, xrange, int, long, print, str, unicode, repr from future_builtins import * chr, str = unichr, unicode from io import open +class object(object): + if hasattr(object, "__doc__"): + __doc__ = object.__doc__ + def __ne__(self, other): + eq = self == other + if eq is _coconut_NotImplemented: + return eq + else: + return not eq class range(object): __slots__ = ("_xrange",) if hasattr(_coconut_xrange, "__doc__"): diff --git a/tests/src/cocotest/agnostic/suite.coco b/tests/src/cocotest/agnostic/suite.coco index 1c04310ce..f49e7c9cc 100644 --- a/tests/src/cocotest/agnostic/suite.coco +++ b/tests/src/cocotest/agnostic/suite.coco @@ -69,6 +69,8 @@ def suite_test(): assert not vector(1, 2) |> vector(3, 4).__eq__ assert not (1, 2) |> vector(1, 2).__eq__ assert vector(vector(4, 3)) == vector(4, 3) + assert not vector(4, 3) != vector(4, 3) + assert vector(4, 3) != (4, 3) assert triangle(3, 4, 5).is_right() assert (.)(triangle(3, 4, 5), "is_right") assert triangle(3, 4, 5) |> .is_right() @@ -290,4 +292,8 @@ def suite_test(): assert reversed((1, 2, 3)) |> fmap$(plus1) |> repr == map(plus1, (1, 2, 3)) |> reversed |> repr assert ident[1:2, 2:3] == (slice(1, 2), slice(2, 3)) == ident |> .[1:2, 2:3] assert ident.method(*(1,), **{"a": 2}) == ((1,), {"a": 2}) == ident |> .method(*(1,), **{"a": 2}) + assert container(1) == container(1) + assert not container(1) != container(1) + assert container(1) != container(2) + assert not container(1) == container(2) return True diff --git a/tests/src/cocotest/agnostic/util.coco b/tests/src/cocotest/agnostic/util.coco index 552540c3f..7f3ca5576 100644 --- a/tests/src/cocotest/agnostic/util.coco +++ b/tests/src/cocotest/agnostic/util.coco @@ -575,11 +575,19 @@ def ret_none(n): def ret_args_kwargs(*args, **kwargs) = (args, kwargs) +# Useful Classes + class identity_operations: def __getitem__(self, args) = args def method(self, *args, **kwargs) = (args, kwargs) ident = identity_operations() +class container: + def __init__(self, x): + self.x = x + def __eq__(self, other) = + isinstance(other, container) and self.x == other.x + # Typing import sys From 6604da60ff87b69fb3dd3cc3418884ea745e7474 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 21 Mar 2017 20:55:26 -0700 Subject: [PATCH 092/176] Adds py_object to builtins --- coconut/constants.py | 1 + coconut/stubs/__coconut__.pyi | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/coconut/constants.py b/coconut/constants.py index 3ca516c68..a764fcd2c 100644 --- a/coconut/constants.py +++ b/coconut/constants.py @@ -423,6 +423,7 @@ def fixpath(path): "py_input", "py_input", "py_int", + "py_object", "py_oct", "py_open", "py_print", diff --git a/coconut/stubs/__coconut__.pyi b/coconut/stubs/__coconut__.pyi index ba007c79c..eefb850c7 100644 --- a/coconut/stubs/__coconut__.pyi +++ b/coconut/stubs/__coconut__.pyi @@ -45,7 +45,7 @@ else: ascii, filter, hex, map, oct, zip, open, chr, str, range = _b.ascii, _b.filter, _b.hex, _b.map, _b.oct, _b.zip, _b.open, _b.chr, _b.str, _b.range -py_chr, py_filter, py_hex, py_input, py_int, py_map, py_oct, py_open, py_print, py_range, py_str, py_zip, py_filter, py_reversed, py_enumerate = _b.chr, _b.filter, _b.hex, _b.input, _b.int, _b.map, _b.oct, _b.open, _b.print, _b.range, _b.str, _b.zip, _b.filter, _b.reversed, _b.enumerate +py_chr, py_filter, py_hex, py_input, py_int, py_map, py_object, py_oct, py_open, py_print, py_range, py_str, py_zip, py_filter, py_reversed, py_enumerate = _b.chr, _b.filter, _b.hex, _b.input, _b.int, _b.map, _b.object, _b.oct, _b.open, _b.print, _b.range, _b.str, _b.zip, _b.filter, _b.reversed, _b.enumerate from functools import reduce From 3bd2846ff479e145100459e62ef3fae3b11a33a1 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 21 Mar 2017 21:48:39 -0700 Subject: [PATCH 093/176] Adds starred data Refs #241. --- coconut/compiler/grammar.py | 20 +++++++++++++------- coconut/compiler/header.py | 19 +++++++++++++++---- tests/src/cocotest/agnostic/suite.coco | 6 +++++- tests/src/cocotest/agnostic/util.coco | 3 ++- 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/coconut/compiler/grammar.py b/coconut/compiler/grammar.py index 06f373bb3..ef1658741 100644 --- a/coconut/compiler/grammar.py +++ b/coconut/compiler/grammar.py @@ -222,21 +222,27 @@ def data_handle(tokens): name, attrs, stmts = tokens else: raise CoconutInternalException("invalid data tokens", tokens) - out = "class " + name + '(_coconut.collections.namedtuple("' + name + '", "' + attrs + '")):\n' + openindent + extra_stmt = "__slots__ = ()\n" + if attrs.startswith("*"): + inheritance = "_coconut_stardata" + extra_stmt += attrs[1:] + " = _coconut.property(_coconut_stardata.__getnewargs__)\n" + else: + inheritance = '_coconut.collections.namedtuple("' + name + '", "' + attrs + '")' + out = "class " + name + "(" + inheritance + "):\n" + openindent rest = None if "simple" in stmts.keys() and len(stmts) == 1: - out += "__slots__ = ()\n" + out += extra_stmt rest = stmts[0] elif "docstring" in stmts.keys() and len(stmts) == 1: - out += stmts[0] + "__slots__ = ()\n" + out += stmts[0] + extra_stmt elif "complex" in stmts.keys() and len(stmts) == 1: - out += "__slots__ = ()\n" + out += extra_stmt rest = "".join(stmts[0]) elif "complex" in stmts.keys() and len(stmts) == 2: - out += stmts[0] + "__slots__ = ()\n" + out += stmts[0] + extra_stmt rest = "".join(stmts[1]) elif "empty" in stmts.keys() and len(stmts) == 1: - out += "__slots__ = ()" + stmts[0] + out += extra_stmt.rstrip() + stmts[0] else: raise CoconutInternalException("invalid inner data tokens", stmts) if rest is not None and rest != "pass\n": @@ -1109,7 +1115,7 @@ class Grammar(object): + (def_match_funcdef | math_match_funcdef) ) - data_args = Optional(lparen.suppress() + Optional(itemlist(~underscore + name, comma)) + rparen.suppress()) + data_args = Optional(lparen.suppress() + Optional(itemlist(~underscore + name, comma) | condense(star + name)) + rparen.suppress()) data_suite = Group(colon.suppress() - ( (newline.suppress() + indent.suppress() + Optional(docstring) + Group(OneOrMore(stmt)) + dedent.suppress())("complex") | (newline.suppress() + indent.suppress() + docstring + dedent.suppress() | docstring)("docstring") diff --git a/coconut/compiler/header.py b/coconut/compiler/header.py index eba6e45b3..01fbb7d01 100644 --- a/coconut/compiler/header.py +++ b/coconut/compiler/header.py @@ -105,14 +105,14 @@ def getheader(which, target="", usehash=None): import sys as _coconut_sys, os.path as _coconut_os_path _coconut_file_path = _coconut_os_path.dirname(_coconut_os_path.abspath(__file__)) _coconut_sys.path.insert(0, _coconut_file_path) -from __coconut__ import _coconut, _coconut_MatchError, _coconut_tail_call, _coconut_tco, _coconut_igetitem, _coconut_compose, _coconut_pipe, _coconut_starpipe, _coconut_backpipe, _coconut_backstarpipe, _coconut_bool_and, _coconut_bool_or, _coconut_minus, _coconut_map, _coconut_partial +from __coconut__ import _coconut, _coconut_MatchError, _coconut_tail_call, _coconut_tco, _coconut_igetitem, _coconut_compose, _coconut_pipe, _coconut_starpipe, _coconut_backpipe, _coconut_backstarpipe, _coconut_bool_and, _coconut_bool_or, _coconut_minus, _coconut_map, _coconut_partial, _coconut_stardata from __coconut__ import * _coconut_sys.path.remove(_coconut_file_path) ''' elif which == "sys": header += r''' import sys as _coconut_sys -from coconut.__coconut__ import _coconut, _coconut_MatchError, _coconut_tail_call, _coconut_tco, _coconut_igetitem, _coconut_compose, _coconut_pipe, _coconut_starpipe, _coconut_backpipe, _coconut_backstarpipe, _coconut_bool_and, _coconut_bool_or, _coconut_minus, _coconut_map, _coconut_partial +from coconut.__coconut__ import _coconut, _coconut_MatchError, _coconut_tail_call, _coconut_tco, _coconut_igetitem, _coconut_compose, _coconut_pipe, _coconut_starpipe, _coconut_backpipe, _coconut_backstarpipe, _coconut_bool_and, _coconut_bool_or, _coconut_minus, _coconut_map, _coconut_partial, _coconut_stardata from coconut.__coconut__ import * ''' elif which == "__coconut__" or which == "code" or which == "file": @@ -144,10 +144,10 @@ class _coconut(object):''' import collections.abc as abc''' if target.startswith("3"): header += r''' - IndexError, NameError, TypeError, ValueError, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, iter, len, list, map, min, max, next, object, range, reversed, set, slice, str, sum, super, tuple, zip, repr = IndexError, NameError, TypeError, ValueError, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, iter, len, list, map, min, max, next, object, range, reversed, set, slice, str, sum, super, tuple, zip, repr''' + IndexError, NameError, TypeError, ValueError, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, iter, len, list, map, min, max, next, object, property, range, reversed, set, slice, str, sum, super, tuple, zip, repr = IndexError, NameError, TypeError, ValueError, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, iter, len, list, map, min, max, next, object, property, range, reversed, set, slice, str, sum, super, tuple, zip, repr''' else: header += r''' - IndexError, NameError, TypeError, ValueError, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, iter, len, list, map, min, max, next, object, range, reversed, set, slice, str, sum, super, tuple, zip, repr, bytearray = IndexError, NameError, TypeError, ValueError, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, iter, len, list, map, min, max, next, object, range, reversed, set, slice, str, sum, super, tuple, zip, staticmethod(repr), bytearray''' + IndexError, NameError, TypeError, ValueError, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, iter, len, list, map, min, max, next, object, property, range, reversed, set, slice, str, sum, super, tuple, zip, repr, bytearray = IndexError, NameError, TypeError, ValueError, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, iter, len, list, map, min, max, next, object, property, range, reversed, set, slice, str, sum, super, tuple, zip, staticmethod(repr), bytearray''' header += r''' class MatchError(Exception): """Pattern-matching error.""" @@ -518,6 +518,17 @@ def __repr__(self): for arg in self._stargs: args.append(_coconut.repr(arg)) return _coconut.repr(self.func) + "$(" + ", ".join(args) + ")" +class _coconut_stardata(tuple): + __slots__ = () + def __new__(cls, *args): + return _coconut.tuple.__new__(cls, args) + @classmethod + def _make(cls, iterable, new=tuple.__new__): + return new(cls, iterable) + def __repr__(self): + return self.__class__.__name__ + _coconut.tuple.__repr__(self) + def __getnewargs__(self): + return _coconut.tuple(self) def datamaker(data_type): """Returns base data constructor of passed data type.""" return _coconut.functools.partial(_coconut.super(data_type, data_type).__new__, data_type) diff --git a/tests/src/cocotest/agnostic/suite.coco b/tests/src/cocotest/agnostic/suite.coco index f49e7c9cc..4390f41b2 100644 --- a/tests/src/cocotest/agnostic/suite.coco +++ b/tests/src/cocotest/agnostic/suite.coco @@ -287,7 +287,7 @@ def suite_test(): assert False assert Just(3) |> map$(-> _*2) |*> Just == Just(6) == Just(3) |> fmap$(-> _*2) assert Nothing() |> map$(-> _*2) |*> Nothing == Nothing() == Nothing() |> fmap$(-> _*2) - assert Tuple(1, 2, 3) != Tuple(1, 2) + assert Elems(1, 2, 3) != Elems(1, 2) assert map(plus1, (1, 2, 3)) |> fmap$(times2) |> repr == map(times2..plus1, (1, 2, 3)) |> repr assert reversed((1, 2, 3)) |> fmap$(plus1) |> repr == map(plus1, (1, 2, 3)) |> reversed |> repr assert ident[1:2, 2:3] == (slice(1, 2), slice(2, 3)) == ident |> .[1:2, 2:3] @@ -296,4 +296,8 @@ def suite_test(): assert not container(1) != container(1) assert container(1) != container(2) assert not container(1) == container(2) + t = Tuple(1, 2) + assert t.elems == (1, 2) + Tuple(x, y) = t + assert x == 1 and y == 2 return True diff --git a/tests/src/cocotest/agnostic/util.coco b/tests/src/cocotest/agnostic/util.coco index 7f3ca5576..e2e0dba06 100644 --- a/tests/src/cocotest/agnostic/util.coco +++ b/tests/src/cocotest/agnostic/util.coco @@ -199,9 +199,10 @@ def is_null(item): return True else: return False -data Tuple(elems): +data Elems(elems): def __new__(cls, *elems) = elems |> datamaker(cls) +data Tuple(*elems) # Factorial: def factorial1(value): From 16f13834b4dd49af65c242dc04aa37a90c4166c2 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 21 Mar 2017 22:10:09 -0700 Subject: [PATCH 094/176] Use _make in datamaker Resolves #242. --- DOCS.md | 2 +- coconut/compiler/header.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/DOCS.md b/DOCS.md index d0fd4e8f0..a79a4a3eb 100644 --- a/DOCS.md +++ b/DOCS.md @@ -1578,7 +1578,7 @@ _Can't be done quickly without Coconut's iterator slicing, which requires many c Coconut provides the `datamaker` function to allow direct access to the base constructor of data types created with the Coconut `data` statement. This is particularly useful when writing alternative constructors for data types by overwriting `__new__`. -Equivalent to: +For non-`data` objects, equivalent to: ```coconut def datamaker(data_type): """Returns base data constructor of data_type.""" diff --git a/coconut/compiler/header.py b/coconut/compiler/header.py index 01fbb7d01..308a3f897 100644 --- a/coconut/compiler/header.py +++ b/coconut/compiler/header.py @@ -531,7 +531,10 @@ def __getnewargs__(self): return _coconut.tuple(self) def datamaker(data_type): """Returns base data constructor of passed data type.""" - return _coconut.functools.partial(_coconut.super(data_type, data_type).__new__, data_type) + if _coconut.isinstance(data_type, _coconut.tuple) and _coconut.hasattr(data_type, "_make"): + return data_type._make + else: + return _coconut.functools.partial(_coconut.super(data_type, data_type).__new__, data_type) def consume(iterable, keep_last=0): """Fully exhaust iterable and return the last keep_last elements.""" return _coconut.collections.deque(iterable, maxlen=keep_last) # fastest way to exhaust an iterator From 9cb23356c03a79d55b490362753236495d4aeada Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 21 Mar 2017 22:48:30 -0700 Subject: [PATCH 095/176] Fixes a slots issue --- coconut/root.py | 1 + 1 file changed, 1 insertion(+) diff --git a/coconut/root.py b/coconut/root.py index c39388fdd..371feb3a3 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -50,6 +50,7 @@ chr, str = unichr, unicode from io import open class object(object): + __slots__ = () if hasattr(object, "__doc__"): __doc__ = object.__doc__ def __ne__(self, other): From 68f17be1bdf5f090df2b28ab7e818621062af3aa Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Wed, 22 Mar 2017 10:59:03 -0700 Subject: [PATCH 096/176] Fix an invalid test --- tests/src/cocotest/agnostic/suite.coco | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/cocotest/agnostic/suite.coco b/tests/src/cocotest/agnostic/suite.coco index 4390f41b2..e7109d75c 100644 --- a/tests/src/cocotest/agnostic/suite.coco +++ b/tests/src/cocotest/agnostic/suite.coco @@ -70,7 +70,7 @@ def suite_test(): assert not (1, 2) |> vector(1, 2).__eq__ assert vector(vector(4, 3)) == vector(4, 3) assert not vector(4, 3) != vector(4, 3) - assert vector(4, 3) != (4, 3) + assert vector(4, 3) == (4, 3) assert triangle(3, 4, 5).is_right() assert (.)(triangle(3, 4, 5), "is_right") assert triangle(3, 4, 5) |> .is_right() From b179f6044b3f6be9f55c6657f7f5b5b99e36dbf2 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Wed, 22 Mar 2017 13:10:42 -0700 Subject: [PATCH 097/176] Remove an incorrect test --- tests/src/cocotest/agnostic/suite.coco | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/src/cocotest/agnostic/suite.coco b/tests/src/cocotest/agnostic/suite.coco index e7109d75c..01cf31595 100644 --- a/tests/src/cocotest/agnostic/suite.coco +++ b/tests/src/cocotest/agnostic/suite.coco @@ -70,7 +70,6 @@ def suite_test(): assert not (1, 2) |> vector(1, 2).__eq__ assert vector(vector(4, 3)) == vector(4, 3) assert not vector(4, 3) != vector(4, 3) - assert vector(4, 3) == (4, 3) assert triangle(3, 4, 5).is_right() assert (.)(triangle(3, 4, 5), "is_right") assert triangle(3, 4, 5) |> .is_right() From 1042a346d5e92d37aea02b31032b9eb7b4c6a8e3 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Wed, 22 Mar 2017 16:24:56 -0700 Subject: [PATCH 098/176] Fixes Python 2 object instance checking --- coconut/root.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/coconut/root.py b/coconut/root.py index 371feb3a3..b3311f0a9 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -45,7 +45,7 @@ PY3_HEADER = r'''py_chr, py_filter, py_hex, py_input, py_int, py_map, py_object, py_oct, py_open, py_print, py_range, py_str, py_zip, py_filter, py_reversed, py_enumerate = chr, filter, hex, input, int, map, object, oct, open, print, range, str, zip, filter, reversed, enumerate ''' PY27_HEADER = PY3_HEADER + r'''py_raw_input, py_xrange = raw_input, xrange -_coconut_NotImplemented, _coconut_raw_input, _coconut_xrange, _coconut_int, _coconut_long, _coconut_print, _coconut_str, _coconut_unicode, _coconut_repr = NotImplemented, raw_input, xrange, int, long, print, str, unicode, repr +_coconut_NotImplemented, _coconut_raw_input, _coconut_xrange, _coconut_int, _coconut_long, _coconut_print, _coconut_str, _coconut_unicode, _coconut_repr, _coconut_object = NotImplemented, raw_input, xrange, int, long, print, str, unicode, repr, object from future_builtins import * chr, str = unichr, unicode from io import open @@ -53,6 +53,9 @@ class object(object): __slots__ = () if hasattr(object, "__doc__"): __doc__ = object.__doc__ + class __metaclass__(type): + def __instancecheck__(cls, inst): + return _coconut.isinstance(inst, _coconut_object) def __ne__(self, other): eq = self == other if eq is _coconut_NotImplemented: From ec7cd8cf9a4c9fa65f66bee961b636e39a8e71b4 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Wed, 22 Mar 2017 19:08:50 -0700 Subject: [PATCH 099/176] Adds full starred data support Refs #241. --- coconut/compiler/compiler.py | 17 +++--- coconut/compiler/grammar.py | 79 +++++++++++++++++++------- coconut/compiler/header.py | 19 ++----- coconut/compiler/matching.py | 14 +++-- coconut/exceptions.py | 8 +++ tests/src/cocotest/agnostic/suite.coco | 32 +++++++++++ tests/src/cocotest/agnostic/util.coco | 17 +++++- 7 files changed, 132 insertions(+), 54 deletions(-) diff --git a/coconut/compiler/compiler.py b/coconut/compiler/compiler.py index dff2ebc62..8948be37c 100644 --- a/coconut/compiler/compiler.py +++ b/coconut/compiler/compiler.py @@ -30,7 +30,6 @@ from coconut.root import * # NOQA import sys -from contextlib import contextmanager from pyparsing import ( ParseBaseException, @@ -416,13 +415,10 @@ def target_info_len2(self): else: return info[:2] - @contextmanager - def handle_deferred_syntax_errs(self, original, loc): + def make_syntax_err(self, err, original): """Handles CoconutDeferredSyntaxError.""" - try: - yield - except CoconutDeferredSyntaxError as err: - raise self.make_err(CoconutSyntaxError, str(err), original, loc) + msg, loc = err.args + return self.make_err(CoconutSyntaxError, msg, original, loc) def make_parse_err(self, err, reformat=True, include_ln=True): """Make a CoconutParseError from a ParseBaseException.""" @@ -445,6 +441,8 @@ def parse(self, inputstring, parser, preargs, postargs): out = self.post(parsed, **postargs) except ParseBaseException as err: raise self.make_parse_err(err) + except CoconutDeferredSyntaxError as err: + raise self.make_syntax_err(err, pre_procd) except RuntimeError as err: raise CoconutException(str(err), extra="try again with --recursion-limit greater than the current " + str(sys.getrecursionlimit())) @@ -1160,11 +1158,10 @@ def name_match_funcdef_handle(self, original, loc, tokens): func, matches, cond = tokens else: raise CoconutInternalException("invalid match function definition tokens", tokens) - matcher = Matcher() + matcher = Matcher(loc) req_args, def_args, star_arg, kwd_args, dubstar_arg = self.split_args_list(original, loc, matches) - with self.handle_deferred_syntax_errs(original, loc): - matcher.match_function(match_to_args_var, match_to_kwargs_var, req_args + def_args, star_arg, kwd_args, dubstar_arg) + matcher.match_function(match_to_args_var, match_to_kwargs_var, req_args + def_args, star_arg, kwd_args, dubstar_arg) if cond is not None: matcher.add_guard(cond) diff --git a/coconut/compiler/grammar.py b/coconut/compiler/grammar.py index ef1658741..c0bfa525b 100644 --- a/coconut/compiler/grammar.py +++ b/coconut/compiler/grammar.py @@ -51,7 +51,10 @@ FollowedBy, ) -from coconut.exceptions import CoconutInternalException +from coconut.exceptions import ( + CoconutInternalException, + CoconutDeferredSyntaxError +) from coconut.terminal import trace from coconut.constants import ( openindent, @@ -213,36 +216,66 @@ def math_funcdef_handle(tokens): raise CoconutInternalException("invalid assignment function definition tokens") -def data_handle(tokens): +def data_handle(original, loc, tokens): """Processes data blocks.""" - if len(tokens) == 2: - name, stmts = tokens - attrs = "" - elif len(tokens) == 3: - name, attrs, stmts = tokens + if len(tokens) == 3: + name, args, stmts = tokens else: raise CoconutInternalException("invalid data tokens", tokens) - extra_stmt = "__slots__ = ()\n" - if attrs.startswith("*"): - inheritance = "_coconut_stardata" - extra_stmt += attrs[1:] + " = _coconut.property(_coconut_stardata.__getnewargs__)\n" - else: - inheritance = '_coconut.collections.namedtuple("' + name + '", "' + attrs + '")' - out = "class " + name + "(" + inheritance + "):\n" + openindent + base_args, starred_arg = [], None + for i, arg in enumerate(args): + if arg.startswith("_"): + raise CoconutDeferredSyntaxError("data fields cannot start with an underscore", loc) + elif arg.startswith("*"): + if i != len(args) - 1: + raise CoconutDeferredSyntaxError("starred data field must come at end", loc) + starred_arg = arg[1:] + else: + base_args.append(arg) + attr_str = " ".join(base_args) + extra_stmts = "__slots__ = ()\n" + if starred_arg is not None: + attr_str += (" " if attr_str else "") + starred_arg + extra_stmts += r'''def __new__(_cls, {all_args}): + {oind}return _coconut.tuple.__new__(_cls, {base_args_tuple} + {starred_arg}) +{cind}@_coconut.classmethod +def _make(cls, iterable, new=_coconut.tuple.__new__, len=_coconut.len): + {oind}result = new(cls, iterable) + if len(result) < {num_base_args}: + {oind}raise _coconut.TypeError("Expected at least 2 arguments, got %d" % len(result)) + {cind}return result +{cind}def _replace(_self, **kwds): + {oind}result = _self._make(_coconut.tuple(_coconut.map(kwds.pop, {quoted_base_args_tuple}, _self)) + kwds.pop("{starred_arg}", self.{starred_arg})) + if kwds: + {oind}raise _coconut.ValueError("Got unexpected field names: %r" % kwds.keys()) + {cind}return result +{cind}@_coconut.property +def {starred_arg}(self): + {oind}return self[{num_base_args}:] +{cind}'''.format( + oind=openindent, + cind=closeindent, + starred_arg=starred_arg, + all_args=", ".join(args), + num_base_args=str(len(base_args)), + base_args_tuple="(" + ", ".join(base_args) + ("," if len(base_args) == 1 else "") + ")", + quoted_base_args_tuple='("' + '", "'.join(base_args) + '"' + ("," if len(base_args) == 1 else "") + ")", + ) + out = "class " + name + '(_coconut.collections.namedtuple("' + name + '", "' + attr_str + '")):\n' + openindent rest = None if "simple" in stmts.keys() and len(stmts) == 1: - out += extra_stmt + out += extra_stmts rest = stmts[0] elif "docstring" in stmts.keys() and len(stmts) == 1: - out += stmts[0] + extra_stmt + out += stmts[0] + extra_stmts elif "complex" in stmts.keys() and len(stmts) == 1: - out += extra_stmt + out += extra_stmts rest = "".join(stmts[0]) elif "complex" in stmts.keys() and len(stmts) == 2: - out += stmts[0] + extra_stmt + out += stmts[0] + extra_stmts rest = "".join(stmts[1]) elif "empty" in stmts.keys() and len(stmts) == 1: - out += extra_stmt.rstrip() + stmts[0] + out += extra_stmts.rstrip() + stmts[0] else: raise CoconutInternalException("invalid inner data tokens", stmts) if rest is not None and rest != "pass\n": @@ -275,7 +308,7 @@ def else_handle(tokens): raise CoconutInternalException("invalid compound else statement tokens", tokens) -def match_handle(o, l, tokens, top=True): +def match_handle(original, loc, tokens, top=True): """Processes match blocks.""" if len(tokens) == 3: matches, item, stmts = tokens @@ -284,7 +317,7 @@ def match_handle(o, l, tokens, top=True): matches, item, cond, stmts = tokens else: raise CoconutInternalException("invalid match statement tokens", tokens) - matching = Matcher() + matching = Matcher(loc) matching.match(matches, match_to_var) if cond: matching.add_guard(cond) @@ -1115,7 +1148,9 @@ class Grammar(object): + (def_match_funcdef | math_match_funcdef) ) - data_args = Optional(lparen.suppress() + Optional(itemlist(~underscore + name, comma) | condense(star + name)) + rparen.suppress()) + data_args = Group(Optional(lparen.suppress() + Optional( + tokenlist(condense(Optional(star) + name), comma) + ) + rparen.suppress())) data_suite = Group(colon.suppress() - ( (newline.suppress() + indent.suppress() + Optional(docstring) + Group(OneOrMore(stmt)) + dedent.suppress())("complex") | (newline.suppress() + indent.suppress() + docstring + dedent.suppress() | docstring)("docstring") diff --git a/coconut/compiler/header.py b/coconut/compiler/header.py index 308a3f897..f136c704b 100644 --- a/coconut/compiler/header.py +++ b/coconut/compiler/header.py @@ -105,14 +105,14 @@ def getheader(which, target="", usehash=None): import sys as _coconut_sys, os.path as _coconut_os_path _coconut_file_path = _coconut_os_path.dirname(_coconut_os_path.abspath(__file__)) _coconut_sys.path.insert(0, _coconut_file_path) -from __coconut__ import _coconut, _coconut_MatchError, _coconut_tail_call, _coconut_tco, _coconut_igetitem, _coconut_compose, _coconut_pipe, _coconut_starpipe, _coconut_backpipe, _coconut_backstarpipe, _coconut_bool_and, _coconut_bool_or, _coconut_minus, _coconut_map, _coconut_partial, _coconut_stardata +from __coconut__ import _coconut, _coconut_MatchError, _coconut_tail_call, _coconut_tco, _coconut_igetitem, _coconut_compose, _coconut_pipe, _coconut_starpipe, _coconut_backpipe, _coconut_backstarpipe, _coconut_bool_and, _coconut_bool_or, _coconut_minus, _coconut_map, _coconut_partial from __coconut__ import * _coconut_sys.path.remove(_coconut_file_path) ''' elif which == "sys": header += r''' import sys as _coconut_sys -from coconut.__coconut__ import _coconut, _coconut_MatchError, _coconut_tail_call, _coconut_tco, _coconut_igetitem, _coconut_compose, _coconut_pipe, _coconut_starpipe, _coconut_backpipe, _coconut_backstarpipe, _coconut_bool_and, _coconut_bool_or, _coconut_minus, _coconut_map, _coconut_partial, _coconut_stardata +from coconut.__coconut__ import _coconut, _coconut_MatchError, _coconut_tail_call, _coconut_tco, _coconut_igetitem, _coconut_compose, _coconut_pipe, _coconut_starpipe, _coconut_backpipe, _coconut_backstarpipe, _coconut_bool_and, _coconut_bool_or, _coconut_minus, _coconut_map, _coconut_partial from coconut.__coconut__ import * ''' elif which == "__coconut__" or which == "code" or which == "file": @@ -144,10 +144,10 @@ class _coconut(object):''' import collections.abc as abc''' if target.startswith("3"): header += r''' - IndexError, NameError, TypeError, ValueError, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, iter, len, list, map, min, max, next, object, property, range, reversed, set, slice, str, sum, super, tuple, zip, repr = IndexError, NameError, TypeError, ValueError, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, iter, len, list, map, min, max, next, object, property, range, reversed, set, slice, str, sum, super, tuple, zip, repr''' + IndexError, NameError, TypeError, ValueError, classmethod, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, iter, len, list, map, min, max, next, object, property, range, reversed, set, slice, str, sum, super, tuple, zip, repr = IndexError, NameError, TypeError, ValueError, classmethod, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, iter, len, list, map, min, max, next, object, property, range, reversed, set, slice, str, sum, super, tuple, zip, repr''' else: header += r''' - IndexError, NameError, TypeError, ValueError, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, iter, len, list, map, min, max, next, object, property, range, reversed, set, slice, str, sum, super, tuple, zip, repr, bytearray = IndexError, NameError, TypeError, ValueError, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, iter, len, list, map, min, max, next, object, property, range, reversed, set, slice, str, sum, super, tuple, zip, staticmethod(repr), bytearray''' + IndexError, NameError, TypeError, ValueError, classmethod, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, iter, len, list, map, min, max, next, object, property, range, reversed, set, slice, str, sum, super, tuple, zip, repr, bytearray = IndexError, NameError, TypeError, ValueError, classmethod, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, iter, len, list, map, min, max, next, object, property, range, reversed, set, slice, str, sum, super, tuple, zip, staticmethod(repr), bytearray''' header += r''' class MatchError(Exception): """Pattern-matching error.""" @@ -518,17 +518,6 @@ def __repr__(self): for arg in self._stargs: args.append(_coconut.repr(arg)) return _coconut.repr(self.func) + "$(" + ", ".join(args) + ")" -class _coconut_stardata(tuple): - __slots__ = () - def __new__(cls, *args): - return _coconut.tuple.__new__(cls, args) - @classmethod - def _make(cls, iterable, new=tuple.__new__): - return new(cls, iterable) - def __repr__(self): - return self.__class__.__name__ + _coconut.tuple.__repr__(self) - def __getnewargs__(self): - return _coconut.tuple(self) def datamaker(data_type): """Returns base data constructor of passed data type.""" if _coconut.isinstance(data_type, _coconut.tuple) and _coconut.hasattr(data_type, "_make"): diff --git a/coconut/compiler/matching.py b/coconut/compiler/matching.py index 71f1b8e24..16f45fdcd 100644 --- a/coconut/compiler/matching.py +++ b/coconut/compiler/matching.py @@ -82,6 +82,7 @@ class Matcher(object): "star": lambda self: self.match_star, } __slots__ = ( + "loc", "position", "checkdefs", "names", @@ -90,8 +91,9 @@ class Matcher(object): "guards", ) - def __init__(self, checkdefs=None, names=None, var_index=0): + def __init__(self, loc, checkdefs=None, names=None, var_index=0): """Creates the matcher.""" + self.loc = loc self.position = 0 self.checkdefs = [] if checkdefs is None: @@ -231,11 +233,11 @@ def match_function(self, args, kwargs, match_args=[], star_arg=None, kwd_args=[] if star_arg is not None: self.match(star_arg, args + "[" + str(len(match_args)) + ":]") self.match_in_kwargs(kwd_args, kwargs) - if dubstar_arg is None: - with self.incremented(): + with self.incremented(): + if dubstar_arg is None: self.add_check("not " + kwargs) - else: - self.add_def(dubstar_arg + " = " + kwargs) + else: + self.match(dubstar_arg, kwargs) def match_in_args_kwargs(self, match_args, args, kwargs, allow_star_args=False): """Matches against args or kwargs.""" @@ -324,7 +326,7 @@ def match_in_kwargs(self, match_args, kwargs): with self.incremented(): self.match(match, tempvar) else: - raise CoconutDeferredSyntaxError("keyword-only pattern-matching function arguments must have names") + raise CoconutDeferredSyntaxError("keyword-only pattern-matching function arguments must have names", self.loc) def match_dict(self, tokens, item): """Matches a dictionary.""" diff --git a/coconut/exceptions.py b/coconut/exceptions.py index 1e3459beb..057e8c758 100644 --- a/coconut/exceptions.py +++ b/coconut/exceptions.py @@ -176,3 +176,11 @@ class CoconutInternalException(CoconutException): class CoconutDeferredSyntaxError(CoconutException): """Deferred Coconut SyntaxError.""" + + def __init__(self, message, loc): + """Creates the Coconut exception.""" + self.args = (message, loc) + + def message(self, message, loc): + """Uses arguments to create the message.""" + return message diff --git a/tests/src/cocotest/agnostic/suite.coco b/tests/src/cocotest/agnostic/suite.coco index 01cf31595..1936e6d02 100644 --- a/tests/src/cocotest/agnostic/suite.coco +++ b/tests/src/cocotest/agnostic/suite.coco @@ -273,6 +273,19 @@ def suite_test(): pass else: assert False + assert no_args_kwargs() + try: + no_args_kwargs(1) + except MatchError: + pass + else: + assert False + try: + no_args_kwargs(a=1) + except MatchError: + pass + else: + assert False a = altclass() assert a.func(1) == 1 assert a.zero(10) == 0 @@ -295,8 +308,27 @@ def suite_test(): assert not container(1) != container(1) assert container(1) != container(2) assert not container(1) == container(2) + assert container_(1) == container_(1) + assert not container_(1) != container_(1) + assert container_(1) != container_(2) + assert not container_(1) == container_(2) t = Tuple(1, 2) assert t.elems == (1, 2) + assert t |> fmap$(-> _+1) == Tuple(2, 3) Tuple(x, y) = t assert x == 1 and y == 2 + p = Pred("name", 1, 2) + assert p.name == "name" + assert p.args == (1, 2) + Pred(name, *args) = p + assert name == "name" + assert args == (1, 2) + p = Quant("name", "var", 1, 2) + assert p.name == "name" + assert p.var == "var" + assert p.args == (1, 2) + Pred(name, var, *args) = p + assert name == "name" + assert var == "var" + assert args == (1, 2) return True diff --git a/tests/src/cocotest/agnostic/util.coco b/tests/src/cocotest/agnostic/util.coco index e2e0dba06..3a77d0610 100644 --- a/tests/src/cocotest/agnostic/util.coco +++ b/tests/src/cocotest/agnostic/util.coco @@ -202,7 +202,6 @@ def is_null(item): data Elems(elems): def __new__(cls, *elems) = elems |> datamaker(cls) -data Tuple(*elems) # Factorial: def factorial1(value): @@ -589,6 +588,12 @@ class container: def __eq__(self, other) = isinstance(other, container) and self.x == other.x +class container_(object): + def __init__(self, x): + self.x = x + def __eq__(self, other) = + isinstance(other, container) and self.x == other.x + # Typing import sys @@ -619,6 +624,8 @@ def head_tail_def_none([head] + tail = [None]) = (head, tail) def kwd_only_x_is_int_def_0(*, x is int = 0) = x +def no_args_kwargs(*(), **{}) = True + # Alternative Class Notation class altclass @@ -664,3 +671,11 @@ class Vars: globs[name] = prevars[name] else: del globs[name] + +# Starred Data + +data Tuple(*elems) + +data Pred(name, *args) + +data Quant(name, var, *args) From 2d9dec0dc6b24c6550786538913d7968437e7b73 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Wed, 22 Mar 2017 20:10:50 -0700 Subject: [PATCH 100/176] Improve starred data support Refs #241. --- coconut/compiler/compiler.py | 211 ++++++--------------------- coconut/compiler/grammar.py | 271 +++++++++++++++++++++++++++-------- coconut/compiler/matching.py | 15 +- coconut/root.py | 4 +- 4 files changed, 269 insertions(+), 232 deletions(-) diff --git a/coconut/compiler/compiler.py b/coconut/compiler/compiler.py index 8948be37c..573f97aba 100644 --- a/coconut/compiler/compiler.py +++ b/coconut/compiler/compiler.py @@ -104,7 +104,6 @@ split_trailing_indent, match_in, transform, - join_args, parse, ) from coconut.compiler.header import ( @@ -171,6 +170,51 @@ def gen_imports(path, impas): out.append("from " + imp_from + " import " + imp + " as " + impas) return out + +def split_args_list(tokens, loc): + """Splits function definition arguments.""" + req_args, def_args, star_arg, kwd_args, dubstar_arg = [], [], None, [], None + pos = 0 + for arg in tokens: + if len(arg) == 1: + if arg[0] == "*": + # star sep (pos = 3) + if pos >= 3: + raise CoconutDeferredSyntaxError("invalid star seperator in function definition", loc) + pos = 3 + else: + # pos arg (pos = 0) + if pos > 0: + raise CoconutDeferredSyntaxError("invalid positional argument in function definition", loc) + req_args.append(arg[0]) + elif len(arg) == 2: + if arg[0] == "*": + # star arg (pos = 2) + if pos >= 2: + raise CoconutDeferredSyntaxError("invalid star argument in function definition", loc) + pos = 2 + star_arg = arg[1] + elif arg[0] == "**": + # dub star arg (pos = 4) + if pos == 4: + raise CoconutDeferredSyntaxError("invalid double star argument in function definition", loc) + pos = 4 + dubstar_arg = arg[1] + else: + # kwd arg (pos = 1 or 3) + if pos <= 1: + pos = 1 + def_args.append((arg[0], arg[1])) + elif pos <= 3: + pos = 3 + kwd_args.append((arg[0], arg[1])) + else: + raise CoconutDeferredSyntaxError("invalid default argument in function definition", loc) + else: + raise CoconutInternalException("invalid function definition argument", arg) + return req_args, def_args, star_arg, kwd_args, dubstar_arg + + # end: HANDLERS #----------------------------------------------------------------------------------------------------------------------- # COMPILER: @@ -246,9 +290,6 @@ def bind(self): self.endline <<= attach(self.endline_ref, self.endline_handle) self.moduledoc_item <<= trace(attach(self.moduledoc, self.set_docstring)) self.name <<= trace(attach(self.base_name, self.name_check)) - self.atom_item <<= trace(attach(self.atom_item_ref, self.item_handle)) - self.no_partial_atom_item <<= trace(attach(self.no_partial_atom_item_ref, self.item_handle)) - self.simple_assign <<= trace(attach(self.simple_assign_ref, self.item_handle)) self.set_literal <<= trace(attach(self.set_literal_ref, self.set_literal_handle)) self.set_letter_literal <<= trace(attach(self.set_letter_literal_ref, self.set_letter_literal_handle)) self.classlist <<= trace(attach(self.classlist_ref, self.classlist_handle)) @@ -263,9 +304,6 @@ def bind(self): self.exec_stmt <<= trace(attach(self.exec_stmt_ref, self.exec_stmt_handle)) self.stmt_lambdef <<= trace(attach(self.stmt_lambdef_ref, self.stmt_lambdef_handle)) self.decoratable_normal_funcdef_stmt <<= trace(attach(self.decoratable_normal_funcdef_stmt_ref, self.decoratable_normal_funcdef_stmt_handle)) - self.function_call <<= trace(attach(self.function_call_tokens, self.function_call_handle)) - self.pipe_expr <<= trace(attach(self.pipe_expr_ref, self.pipe_handle)) - self.no_chain_pipe_expr <<= trace(attach(self.no_chain_pipe_expr_ref, self.pipe_handle)) self.typedef <<= trace(attach(self.typedef_ref, self.typedef_handle)) self.typedef_default <<= trace(attach(self.typedef_default_ref, self.typedef_handle)) self.unsafe_typedef_default <<= trace(attach(self.unsafe_typedef_default_ref, self.unsafe_typedef_handle)) @@ -950,54 +988,6 @@ def endline_handle(self, original, loc, tokens): out = self.wrap_line_number(self.adjust(lineno(loc, original))) + out return out - def item_handle(self, original, loc, tokens): - """Processes items.""" - out = tokens.pop(0) - for trailer in tokens: - if isinstance(trailer, str): - out += trailer - elif len(trailer) == 1: - if trailer[0] == "$[]": - out = "_coconut.functools.partial(_coconut_igetitem, " + out + ")" - elif trailer[0] == "$": - out = "_coconut.functools.partial(_coconut.functools.partial, " + out + ")" - elif trailer[0] == "[]": - out = "_coconut.functools.partial(_coconut.operator.getitem, " + out + ")" - elif trailer[0] == ".": - out = "_coconut.functools.partial(_coconut.getattr, " + out + ")" - else: - raise CoconutInternalException("invalid trailer symbol", trailer[0]) - elif len(trailer) == 2: - if trailer[0] == "$(": - args = trailer[1][1:-1] - if args: - out = "_coconut.functools.partial(" + out + ", " + args + ")" - else: - raise self.make_err(CoconutSyntaxError, "a partial application argument is required", original, loc) - elif trailer[0] == "$[": - out = "_coconut_igetitem(" + out + ", " + trailer[1] + ")" - elif trailer[0] == "$(?": - pos_args, star_args, kwd_args, dubstar_args = self.split_function_call(original, loc, trailer[1]) - extra_args_str = join_args(star_args, kwd_args, dubstar_args) - argdict_pairs = [] - for i in range(len(pos_args)): - if pos_args[i] != "?": - argdict_pairs.append(str(i) + ": " + pos_args[i]) - if argdict_pairs or extra_args_str: - out = ("_coconut_partial(" - + out - + ", {" + ", ".join(argdict_pairs) + "}" - + ", " + str(len(pos_args)) - + (", " if extra_args_str else "") + extra_args_str - + ")") - else: - raise self.make_err(CoconutSyntaxError, "a non-? partial application argument is required", original, loc) - else: - raise CoconutInternalException("invalid special trailer", trailer[0]) - else: - raise CoconutInternalException("invalid trailer tokens", trailer) - return out - def augassign_handle(self, tokens): """Processes assignments.""" if len(tokens) == 3: @@ -1160,7 +1150,7 @@ def name_match_funcdef_handle(self, original, loc, tokens): raise CoconutInternalException("invalid match function definition tokens", tokens) matcher = Matcher(loc) - req_args, def_args, star_arg, kwd_args, dubstar_arg = self.split_args_list(original, loc, matches) + req_args, def_args, star_arg, kwd_args, dubstar_arg = split_args_list(matches, loc) matcher.match_function(match_to_args_var, match_to_kwargs_var, req_args + def_args, star_arg, kwd_args, dubstar_arg) if cond is not None: @@ -1379,115 +1369,6 @@ def decoratable_normal_funcdef_stmt_handle(self, tokens): out += func_name + " = " + undotted_name + "\n" return out - def split_args_list(self, original, loc, tokens): - """Splits function definition arguments.""" - req_args, def_args, star_arg, kwd_args, dubstar_arg = [], [], None, [], None - pos = 0 - for arg in tokens: - if len(arg) == 1: - if arg[0] == "*": - # star sep (pos = 3) - if pos >= 3: - raise self.make_err(CoconutSyntaxError, "invalid star seperator in function definition", original, loc) - pos = 3 - else: - # pos arg (pos = 0) - if pos > 0: - raise self.make_err(CoconutSyntaxError, "invalid positional argument in function definition", original, loc) - req_args.append(arg[0]) - elif len(arg) == 2: - if arg[0] == "*": - # star arg (pos = 2) - if pos >= 2: - raise self.make_err(CoconutSyntaxError, "invalid star argument in function definition", original, loc) - pos = 2 - star_arg = arg[1] - elif arg[0] == "**": - # dub star arg (pos = 4) - if pos == 4: - raise self.make_err(CoconutSyntaxError, "invalid double star argument in function definition", original, loc) - pos = 4 - dubstar_arg = arg[1] - else: - # kwd arg (pos = 1 or 3) - if pos <= 1: - pos = 1 - def_args.append((arg[0], arg[1])) - elif pos <= 3: - pos = 3 - kwd_args.append((arg[0], arg[1])) - else: - raise self.make_err(CoconutSyntaxError, "invalid default argument in function definition", original, loc) - else: - raise CoconutInternalException("invalid function definition argument", arg) - return req_args, def_args, star_arg, kwd_args, dubstar_arg - - def split_function_call(self, original, loc, tokens): - """Split into positional arguments and keyword arguments.""" - pos_args = [] - star_args = [] - kwd_args = [] - dubstar_args = [] - for arg in tokens: - argstr = "".join(arg) - if len(arg) == 1: - if kwd_args or dubstar_args: - raise self.make_err(CoconutSyntaxError, "positional argument after keyword argument", original, loc) - pos_args.append(argstr) - elif len(arg) == 2: - if arg[0] == "*": - if dubstar_args: - raise self.make_err(CoconutSyntaxError, "star unpacking after double star unpacking", original, loc) - star_args.append(argstr) - elif arg[0] == "**": - dubstar_args.append(argstr) - else: - kwd_args.append(argstr) - else: - raise CoconutInternalException("invalid function call argument", arg) - return pos_args, star_args, kwd_args, dubstar_args - - def function_call_handle(self, original, loc, tokens): - """Properly order call arguments.""" - return "(" + join_args(*self.split_function_call(original, loc, tokens)) + ")" - - def pipe_item_split(self, original, loc, tokens): - """Split a partial trailer.""" - if len(tokens) == 1: - return tokens[0] - elif len(tokens) == 2: - func, args = tokens - pos_args, star_args, kwd_args, dubstar_args = self.split_function_call(original, loc, args) - return func, join_args(pos_args, star_args), join_args(kwd_args, dubstar_args) - else: - raise CoconutInternalException("invalid partial trailer", tokens) - - def pipe_handle(self, original, loc, tokens, **kwargs): - """Processes pipe calls.""" - if set(kwargs) > set(("top",)): - complain(CoconutInternalException("unknown pipe_handle keyword arguments", kwargs)) - top = kwargs.get("top", True) - if len(tokens) == 1: - func = self.pipe_item_split(original, loc, tokens.pop()) - if top and isinstance(func, tuple): - return "_coconut.functools.partial(" + join_args(func) + ")" - else: - return func - else: - func = self.pipe_item_split(original, loc, tokens.pop()) - op = tokens.pop() - if op == "|>" or op == "|*>": - star = "*" if op == "|*>" else "" - if isinstance(func, tuple): - return func[0] + "(" + join_args((func[1], star + self.pipe_handle(original, loc, tokens), func[2])) + ")" - else: - return "(" + func + ")(" + star + self.pipe_handle(original, loc, tokens) + ")" - elif op == "<|" or op == "<*|": - star = "*" if op == "<*|" else "" - return self.pipe_handle(original, loc, [[func], "|" + star + ">", [self.pipe_handle(original, loc, tokens, top=False)]]) - else: - raise CoconutInternalException("invalid pipe operator", op) - def unsafe_typedef_handle(self, tokens): """Handles unsafe type annotations.""" return self.typedef_handle(tokens.asList() + [","]) diff --git a/coconut/compiler/grammar.py b/coconut/compiler/grammar.py index c0bfa525b..58129b930 100644 --- a/coconut/compiler/grammar.py +++ b/coconut/compiler/grammar.py @@ -14,6 +14,7 @@ # Table of Contents: # - Imports # - Setup +# - Helpers # - Handlers # - Main Grammar # - Extra Grammar @@ -53,7 +54,7 @@ from coconut.exceptions import ( CoconutInternalException, - CoconutDeferredSyntaxError + CoconutDeferredSyntaxError, ) from coconut.terminal import trace from coconut.constants import ( @@ -84,6 +85,7 @@ itemlist, longest, exprlist, + join_args, ) # end: IMPORTS @@ -100,6 +102,86 @@ # end: SETUP #----------------------------------------------------------------------------------------------------------------------- +# HELPERS: +#----------------------------------------------------------------------------------------------------------------------- + + +def split_function_call(tokens, loc): + """Split into positional arguments and keyword arguments.""" + pos_args = [] + star_args = [] + kwd_args = [] + dubstar_args = [] + for arg in tokens: + argstr = "".join(arg) + if len(arg) == 1: + if kwd_args or dubstar_args: + raise CoconutDeferredSyntaxError("positional argument after keyword argument", loc) + pos_args.append(argstr) + elif len(arg) == 2: + if arg[0] == "*": + if dubstar_args: + raise CoconutDeferredSyntaxError("star unpacking after double star unpacking", loc) + star_args.append(argstr) + elif arg[0] == "**": + dubstar_args.append(argstr) + else: + kwd_args.append(argstr) + else: + raise CoconutInternalException("invalid function call argument", arg) + return pos_args, star_args, kwd_args, dubstar_args + + +def pipe_item_split(tokens, loc): + """Split a partial trailer.""" + if len(tokens) == 1: + return tokens[0] + elif len(tokens) == 2: + func, args = tokens + pos_args, star_args, kwd_args, dubstar_args = split_function_call(args, loc) + return func, join_args(pos_args, star_args), join_args(kwd_args, dubstar_args) + else: + raise CoconutInternalException("invalid partial trailer", tokens) + + +def infix_error(tokens): + """Raises inner infix error.""" + raise CoconutInternalException("invalid inner infix tokens", tokens) + + +def get_infix_items(tokens, callback=infix_error): + """Performs infix token processing.""" + if len(tokens) < 3: + raise CoconutInternalException("invalid infix tokens", tokens) + else: + items = [] + for item in tokens[0]: + items.append(item) + for item in tokens[2]: + items.append(item) + if len(tokens) > 3: + items.append(callback([[]] + tokens[3:])) + args = [] + for arg in items: + if arg: + args.append(arg) + return tokens[1], args + + +def case_to_match(tokens, item): + """Converts case tokens to match tokens.""" + if len(tokens) == 2: + matches, stmts = tokens + return matches, item, stmts + elif len(tokens) == 3: + matches, cond, stmts = tokens + return matches, item, cond, stmts + else: + raise CoconutInternalException("invalid case match tokens", tokens) + + +# end: HELPERS +#----------------------------------------------------------------------------------------------------------------------- # HANDLERS: #----------------------------------------------------------------------------------------------------------------------- @@ -112,6 +194,87 @@ def add_paren_handle(tokens): raise CoconutInternalException("invalid tokens for parentheses adding", tokens) +def function_call_handle(original, loc, tokens): + """Properly order call arguments.""" + return "(" + join_args(*split_function_call(tokens, loc)) + ")" + + +def item_handle(original, loc, tokens): + """Processes items.""" + out = tokens.pop(0) + for trailer in tokens: + if isinstance(trailer, str): + out += trailer + elif len(trailer) == 1: + if trailer[0] == "$[]": + out = "_coconut.functools.partial(_coconut_igetitem, " + out + ")" + elif trailer[0] == "$": + out = "_coconut.functools.partial(_coconut.functools.partial, " + out + ")" + elif trailer[0] == "[]": + out = "_coconut.functools.partial(_coconut.operator.getitem, " + out + ")" + elif trailer[0] == ".": + out = "_coconut.functools.partial(_coconut.getattr, " + out + ")" + else: + raise CoconutInternalException("invalid trailer symbol", trailer[0]) + elif len(trailer) == 2: + if trailer[0] == "$(": + args = trailer[1][1:-1] + if args: + out = "_coconut.functools.partial(" + out + ", " + args + ")" + else: + raise CoconutDeferredSyntaxError("a partial application argument is required", loc) + elif trailer[0] == "$[": + out = "_coconut_igetitem(" + out + ", " + trailer[1] + ")" + elif trailer[0] == "$(?": + pos_args, star_args, kwd_args, dubstar_args = split_function_call(trailer[1], loc) + extra_args_str = join_args(star_args, kwd_args, dubstar_args) + argdict_pairs = [] + for i in range(len(pos_args)): + if pos_args[i] != "?": + argdict_pairs.append(str(i) + ": " + pos_args[i]) + if argdict_pairs or extra_args_str: + out = ("_coconut_partial(" + + out + + ", {" + ", ".join(argdict_pairs) + "}" + + ", " + str(len(pos_args)) + + (", " if extra_args_str else "") + extra_args_str + + ")") + else: + raise CoconutDeferredSyntaxError("a non-? partial application argument is required", loc) + else: + raise CoconutInternalException("invalid special trailer", trailer[0]) + else: + raise CoconutInternalException("invalid trailer tokens", trailer) + return out + + +def pipe_handle(original, loc, tokens, **kwargs): + """Processes pipe calls.""" + if set(kwargs) > set(("top",)): + complain(CoconutInternalException("unknown pipe_handle keyword arguments", kwargs)) + top = kwargs.get("top", True) + if len(tokens) == 1: + func = pipe_item_split(tokens.pop(), loc) + if top and isinstance(func, tuple): + return "_coconut.functools.partial(" + join_args(func) + ")" + else: + return func + else: + func = pipe_item_split(tokens.pop(), loc) + op = tokens.pop() + if op == "|>" or op == "|*>": + star = "*" if op == "|*>" else "" + if isinstance(func, tuple): + return func[0] + "(" + join_args((func[1], star + pipe_handle(original, loc, tokens), func[2])) + ")" + else: + return "(" + func + ")(" + star + pipe_handle(original, loc, tokens) + ")" + elif op == "<|" or op == "<*|": + star = "*" if op == "<*|" else "" + return pipe_handle(original, loc, [[func], "|" + star + ">", [pipe_handle(original, loc, tokens, top=False)]]) + else: + raise CoconutInternalException("invalid pipe operator", op) + + def attr_handle(tokens): """Processes attrgetter literals.""" if len(tokens) == 1: @@ -141,36 +304,12 @@ def chain_handle(tokens): return "_coconut.itertools.chain.from_iterable(" + lazy_list_handle(tokens) + ")" -def infix_error(tokens): - """Raises inner infix error.""" - raise CoconutInternalException("invalid inner infix tokens", tokens) - - def infix_handle(tokens): """Processes infix calls.""" func, args = get_infix_items(tokens, infix_handle) return "(" + func + ")(" + ", ".join(args) + ")" -def get_infix_items(tokens, callback=infix_error): - """Performs infix token processing.""" - if len(tokens) < 3: - raise CoconutInternalException("invalid infix tokens", tokens) - else: - items = [] - for item in tokens[0]: - items.append(item) - for item in tokens[2]: - items.append(item) - if len(tokens) > 3: - items.append(callback([[]] + tokens[3:])) - args = [] - for arg in items: - if arg: - args.append(arg) - return tokens[1], args - - def op_funcdef_handle(tokens): """Processes infix defs.""" func, base_args = get_infix_items(tokens) @@ -228,7 +367,7 @@ def data_handle(original, loc, tokens): raise CoconutDeferredSyntaxError("data fields cannot start with an underscore", loc) elif arg.startswith("*"): if i != len(args) - 1: - raise CoconutDeferredSyntaxError("starred data field must come at end", loc) + raise CoconutDeferredSyntaxError("starred data field must come last", loc) starred_arg = arg[1:] else: base_args.append(arg) @@ -236,7 +375,8 @@ def data_handle(original, loc, tokens): extra_stmts = "__slots__ = ()\n" if starred_arg is not None: attr_str += (" " if attr_str else "") + starred_arg - extra_stmts += r'''def __new__(_cls, {all_args}): + if base_args: + extra_stmts += r'''def __new__(_cls, {all_args}): {oind}return _coconut.tuple.__new__(_cls, {base_args_tuple} + {starred_arg}) {cind}@_coconut.classmethod def _make(cls, iterable, new=_coconut.tuple.__new__, len=_coconut.len): @@ -253,14 +393,33 @@ def _make(cls, iterable, new=_coconut.tuple.__new__, len=_coconut.len): def {starred_arg}(self): {oind}return self[{num_base_args}:] {cind}'''.format( - oind=openindent, - cind=closeindent, - starred_arg=starred_arg, - all_args=", ".join(args), - num_base_args=str(len(base_args)), - base_args_tuple="(" + ", ".join(base_args) + ("," if len(base_args) == 1 else "") + ")", - quoted_base_args_tuple='("' + '", "'.join(base_args) + '"' + ("," if len(base_args) == 1 else "") + ")", - ) + oind=openindent, + cind=closeindent, + starred_arg=starred_arg, + all_args=", ".join(args), + num_base_args=str(len(base_args)), + base_args_tuple="(" + ", ".join(base_args) + ("," if len(base_args) == 1 else "") + ")", + quoted_base_args_tuple='("' + '", "'.join(base_args) + '"' + ("," if len(base_args) == 1 else "") + ")", + ) + else: + extra_stmts += r'''def __new__(_cls, *{arg}): + {oind}return _coconut.tuple.__new__(_cls, {arg}) +{cind}@_coconut.classmethod +def _make(cls, iterable, new=_coconut.tuple.__new__, len=None): + {oind}return new(cls, iterable) +{cind}def _replace(_self, **kwds): + {oind}result = self._make(kwds.pop("{arg}", _self)) + if kwds: + {oind}raise _coconut.ValueError("Got unexpected field names: %r" % kwds.keys()) + {cind}return result +{cind}@_coconut.property +def {arg}(self): + {oind}return self[:] +{cind}'''.format( + oind=openindent, + cind=closeindent, + arg=starred_arg, + ) out = "class " + name + '(_coconut.collections.namedtuple("' + name + '", "' + attr_str + '")):\n' + openindent rest = None if "simple" in stmts.keys() and len(stmts) == 1: @@ -331,18 +490,6 @@ def match_handle(original, loc, tokens, top=True): return out -def case_to_match(tokens, item): - """Converts case tokens to match tokens.""" - if len(tokens) == 2: - matches, stmts = tokens - return matches, item, stmts - elif len(tokens) == 3: - matches, cond, stmts = tokens - return matches, item, cond, stmts - else: - raise CoconutInternalException("invalid case match tokens", tokens) - - def case_handle(o, l, tokens): """Processes case blocks.""" if len(tokens) == 2: @@ -732,12 +879,12 @@ class Grammar(object): | match + Optional(equals.suppress() + test) ), comma)))) - function_call = Forward() function_call_tokens = lparen.suppress() + Optional( Group(attach(addspace(test + comp_for), add_paren_handle)) | tokenlist(Group(dubstar + test | star + test | name + default | test), comma) | Group(op_item) ) + rparen.suppress() + function_call = attach(function_call_tokens, function_call_handle) questionmark_call_tokens = Group(tokenlist(Group( questionmark | dubstar + test | star + test | name + default | test ), comma)) @@ -825,15 +972,12 @@ class Grammar(object): complex_trailer = partial_trailer | complex_trailer_no_partial trailer = simple_trailer | complex_trailer - atom_item = Forward() - atom_item_ref = atom + ZeroOrMore(trailer) - no_partial_atom_item = Forward() - no_partial_atom_item_ref = atom + ZeroOrMore(complex_trailer_no_partial) - simple_assign = Forward() - simple_assign_ref = maybeparens(lparen, - (name | passthrough_atom) - + ZeroOrMore(ZeroOrMore(complex_trailer) + OneOrMore(simple_trailer)), - rparen) + atom_item = attach(atom + ZeroOrMore(trailer), item_handle) + no_partial_atom_item = attach(atom + ZeroOrMore(complex_trailer_no_partial), item_handle) + simple_assign = attach(maybeparens(lparen, + (name | passthrough_atom) + + ZeroOrMore(ZeroOrMore(complex_trailer) + OneOrMore(simple_trailer)), + rparen), item_handle) simple_assignlist = maybeparens(lparen, itemlist(simple_assign, comma), rparen) assignlist = Forward() @@ -884,15 +1028,13 @@ class Grammar(object): | attach(Group(Optional(or_expr)) + infix_op + Group(Optional(no_chain_infix_expr)), infix_handle) ) - pipe_expr = Forward() pipe_item = Group(no_partial_atom_item + partial_trailer_tokens) + pipe_op | Group(infix_expr) + pipe_op last_pipe_item = Group(longest(no_partial_atom_item + partial_trailer_tokens, infix_expr)) - pipe_expr_ref = OneOrMore(pipe_item) + last_pipe_item + pipe_expr = attach(OneOrMore(pipe_item) + last_pipe_item, pipe_handle) expr <<= infix_expr + ~pipe_op | pipe_expr - no_chain_pipe_expr = Forward() no_chain_pipe_item = Group(no_partial_atom_item + partial_trailer_tokens) + pipe_op | Group(no_chain_infix_expr) + pipe_op no_chain_last_pipe_item = Group(longest(no_partial_atom_item + partial_trailer_tokens, no_chain_infix_expr)) - no_chain_pipe_expr_ref = OneOrMore(no_chain_pipe_item) + no_chain_last_pipe_item + no_chain_pipe_expr = attach(OneOrMore(no_chain_pipe_item) + no_chain_last_pipe_item, pipe_handle) no_chain_expr <<= no_chain_infix_expr + ~pipe_op | no_chain_pipe_expr star_expr_ref = condense(star + expr) @@ -1013,6 +1155,11 @@ class Grammar(object): + Optional(Group(OneOrMore(comma.suppress() + match))) + Optional(comma.suppress()) ) + matchlist_data = ( + Optional(Group(OneOrMore(match + comma.suppress())), default=()) + + star.suppress() + match + + Optional(comma.suppress()) + ) | matchlist_list match_const = const_atom | condense(equals.suppress() + atom_item) matchlist_set = Group(Optional(tokenlist(match_const, comma))) @@ -1043,7 +1190,7 @@ class Grammar(object): | iter_match | series_match | star_match - | (name + lparen.suppress() + matchlist_list + rparen.suppress())("data") + | (name + lparen.suppress() + matchlist_data + rparen.suppress())("data") | name("var") )) matchlist_trailer = base_match + OneOrMore(Keyword("as") + name | Keyword("is") + atom_item) diff --git a/coconut/compiler/matching.py b/coconut/compiler/matching.py index 16f45fdcd..16729a6b5 100644 --- a/coconut/compiler/matching.py +++ b/coconut/compiler/matching.py @@ -482,10 +482,21 @@ def match_set(self, tokens, item): def match_data(self, tokens, item): """Matches a data type.""" - data_type, matches = tokens + if len(tokens) == 2: + data_type, matches = tokens + star_match = None + elif len(tokens) == 3: + data_type, matches, star_match = tokens + else: + raise CoconutInternalException("invalid data match tokens", tokens) self.add_check("_coconut.isinstance(" + item + ", " + data_type + ")") - self.add_check("_coconut.len(" + item + ") == " + str(len(matches))) + if star_match is None: + self.add_check("_coconut.len(" + item + ") == " + str(len(matches))) + elif len(matches): + self.add_check("_coconut.len(" + item + ") >= " + str(len(matches))) self.match_all_in(matches, item) + if star_match is not None: + self.match(star_match, item + "[" + str(len(matches)) + ":]") def match_paren(self, tokens, item): """Matches a paren.""" diff --git a/coconut/root.py b/coconut/root.py index b3311f0a9..c4622588d 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -53,9 +53,7 @@ class object(object): __slots__ = () if hasattr(object, "__doc__"): __doc__ = object.__doc__ - class __metaclass__(type): - def __instancecheck__(cls, inst): - return _coconut.isinstance(inst, _coconut_object) + __class__ = object def __ne__(self, other): eq = self == other if eq is _coconut_NotImplemented: From 327901e32772fc563554c7c2075a91d6e3a852be Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Wed, 22 Mar 2017 20:16:15 -0700 Subject: [PATCH 101/176] Fix Matcher error --- coconut/compiler/matching.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coconut/compiler/matching.py b/coconut/compiler/matching.py index 16729a6b5..6d0edc232 100644 --- a/coconut/compiler/matching.py +++ b/coconut/compiler/matching.py @@ -109,7 +109,7 @@ def __init__(self, loc, checkdefs=None, names=None, var_index=0): def duplicate(self): """Duplicates the matcher to others.""" - other = Matcher(self.checkdefs, self.names, self.var_index) + other = Matcher(self.loc, self.checkdefs, self.names, self.var_index) other.insert_check(0, "not " + match_check_var) self.others.append(other) return other From 8dc6e2e2cdd0ded44707d161e342e8c1074492a1 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Thu, 23 Mar 2017 00:54:17 -0700 Subject: [PATCH 102/176] Fix object overriding --- coconut/compiler/header.py | 4 ++-- coconut/root.py | 21 +++++++++++++++++---- tests/src/cocotest/agnostic/main.coco | 15 +++++++++++++++ tests/src/cocotest/agnostic/util.coco | 2 +- 4 files changed, 35 insertions(+), 7 deletions(-) diff --git a/coconut/compiler/header.py b/coconut/compiler/header.py index f136c704b..7fa5e37a6 100644 --- a/coconut/compiler/header.py +++ b/coconut/compiler/header.py @@ -144,10 +144,10 @@ class _coconut(object):''' import collections.abc as abc''' if target.startswith("3"): header += r''' - IndexError, NameError, TypeError, ValueError, classmethod, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, iter, len, list, map, min, max, next, object, property, range, reversed, set, slice, str, sum, super, tuple, zip, repr = IndexError, NameError, TypeError, ValueError, classmethod, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, iter, len, list, map, min, max, next, object, property, range, reversed, set, slice, str, sum, super, tuple, zip, repr''' + IndexError, NameError, TypeError, ValueError, classmethod, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, issubclass, iter, len, list, map, min, max, next, object, property, range, reversed, set, slice, str, sum, super, tuple, zip, repr = IndexError, NameError, TypeError, ValueError, classmethod, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, issubclass, iter, len, list, map, min, max, next, object, property, range, reversed, set, slice, str, sum, super, tuple, zip, repr''' else: header += r''' - IndexError, NameError, TypeError, ValueError, classmethod, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, iter, len, list, map, min, max, next, object, property, range, reversed, set, slice, str, sum, super, tuple, zip, repr, bytearray = IndexError, NameError, TypeError, ValueError, classmethod, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, iter, len, list, map, min, max, next, object, property, range, reversed, set, slice, str, sum, super, tuple, zip, staticmethod(repr), bytearray''' + IndexError, NameError, TypeError, ValueError, classmethod, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, issubclass, iter, len, list, map, min, max, next, object, property, range, reversed, set, slice, str, sum, super, tuple, zip, repr, bytearray = IndexError, NameError, TypeError, ValueError, classmethod, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, issubclass, iter, len, list, map, min, max, next, object, property, range, reversed, set, slice, str, sum, super, tuple, zip, staticmethod(repr), bytearray''' header += r''' class MatchError(Exception): """Pattern-matching error.""" diff --git a/coconut/root.py b/coconut/root.py index c4622588d..f2af17c54 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -49,17 +49,28 @@ from future_builtins import * chr, str = unichr, unicode from io import open -class object(object): +class _coconut_base_object(_coconut_object): __slots__ = () - if hasattr(object, "__doc__"): - __doc__ = object.__doc__ - __class__ = object def __ne__(self, other): eq = self == other if eq is _coconut_NotImplemented: return eq else: return not eq +class object(_coconut_base_object): + __slots__ = () + if hasattr(_coconut_object, "__doc__"): + __doc__ = _coconut_object.__doc__ + class __metaclass__(type): + def __new__(cls, name, bases, dict): + if dict.get("__metaclass__") is not cls: + cls, bases = type, tuple(b if b is not _coconut_new_object else _coconut_base_object for b in bases) + return type.__new__(cls, name, bases, dict) + def __instancecheck__(cls, inst): + return _coconut.isinstance(inst, _coconut_object) + def __subclasscheck__(cls, subcls): + return _coconut.issubclass(subcls, _coconut_object) +_coconut_new_object = object class range(object): __slots__ = ("_xrange",) if hasattr(_coconut_xrange, "__doc__"): @@ -113,6 +124,8 @@ class int(_coconut_int): class __metaclass__(type): def __instancecheck__(cls, inst): return _coconut.isinstance(inst, (_coconut_int, _coconut_long)) + def __subclasscheck__(cls, subcls): + return _coconut.issubclass(subcls, (_coconut_int, _coconut_long)) from functools import wraps as _coconut_wraps @_coconut_wraps(_coconut_print) def print(*args, **kwargs): diff --git a/tests/src/cocotest/agnostic/main.coco b/tests/src/cocotest/agnostic/main.coco index ef9d6318e..b70deaf6b 100644 --- a/tests/src/cocotest/agnostic/main.coco +++ b/tests/src/cocotest/agnostic/main.coco @@ -322,6 +322,21 @@ def main_test(): assert {1, 2, 3} |> fmap$(-> _+1) == {2, 3, 4} assert [[1, 2, 3]] |> fmap$((+)$([0])) == [[0, 1, 2, 3]] assert range(3) |> fmap$(-> _ + 1) |> tuple == (1, 2, 3) + assert issubclass(int, py_int) + class pyobjsub(py_object) + class objsub(object) + assert issubclass(pyobjsub, object) + assert not issubclass(pyobjsub, objsub) + assert issubclass(objsub, object) + assert issubclass(objsub, py_object) + assert not issubclass(objsub, pyobjsub) + pos = pyobjsub() + os = objsub() + assert isinstance(pos, object) + assert not isinstance(pos, objsub) + assert isinstance(os, objsub) + assert isinstance(os, object) + assert not isinstance(os, pyobjsub) return True def main(*args): diff --git a/tests/src/cocotest/agnostic/util.coco b/tests/src/cocotest/agnostic/util.coco index 3a77d0610..fa4b47da2 100644 --- a/tests/src/cocotest/agnostic/util.coco +++ b/tests/src/cocotest/agnostic/util.coco @@ -592,7 +592,7 @@ class container_(object): def __init__(self, x): self.x = x def __eq__(self, other) = - isinstance(other, container) and self.x == other.x + isinstance(other, container_) and self.x == other.x # Typing From dc2535a05be9a3c8499775b0d3815e0b10f54afb Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Thu, 23 Mar 2017 01:46:05 -0700 Subject: [PATCH 103/176] Further improve starred data --- coconut/compiler/grammar.py | 11 +++++++++++ tests/src/cocotest/agnostic/suite.coco | 19 ++++++++++++++----- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/coconut/compiler/grammar.py b/coconut/compiler/grammar.py index 58129b930..175c429b4 100644 --- a/coconut/compiler/grammar.py +++ b/coconut/compiler/grammar.py @@ -384,6 +384,10 @@ def _make(cls, iterable, new=_coconut.tuple.__new__, len=_coconut.len): if len(result) < {num_base_args}: {oind}raise _coconut.TypeError("Expected at least 2 arguments, got %d" % len(result)) {cind}return result +{cind}def _asdict(self): + {oind}return _coconut.collections.OrderedDict((f, _coconut.getattr(self, f)) for f in self._fields) +{cind}def __repr__(self): + {oind}return "{name}({args_for_repr})".format(**self._asdict()) {cind}def _replace(_self, **kwds): {oind}result = _self._make(_coconut.tuple(_coconut.map(kwds.pop, {quoted_base_args_tuple}, _self)) + kwds.pop("{starred_arg}", self.{starred_arg})) if kwds: @@ -395,6 +399,8 @@ def {starred_arg}(self): {cind}'''.format( oind=openindent, cind=closeindent, + name=name, + args_for_repr=", ".join(arg + "={" + arg.lstrip("*") + "}" for arg in args), starred_arg=starred_arg, all_args=", ".join(args), num_base_args=str(len(base_args)), @@ -407,6 +413,10 @@ def {starred_arg}(self): {cind}@_coconut.classmethod def _make(cls, iterable, new=_coconut.tuple.__new__, len=None): {oind}return new(cls, iterable) +{cind}def _asdict(self): + {oind}return _coconut.collections.OrderedDict([("{arg}", self[:])] +{cind}def __repr__(self): + {oind}return "{name}(*{arg}=%r)" % (self[:],) {cind}def _replace(_self, **kwds): {oind}result = self._make(kwds.pop("{arg}", _self)) if kwds: @@ -418,6 +428,7 @@ def {arg}(self): {cind}'''.format( oind=openindent, cind=closeindent, + name=name, arg=starred_arg, ) out = "class " + name + '(_coconut.collections.namedtuple("' + name + '", "' + attr_str + '")):\n' + openindent diff --git a/tests/src/cocotest/agnostic/suite.coco b/tests/src/cocotest/agnostic/suite.coco index 1936e6d02..01a52a103 100644 --- a/tests/src/cocotest/agnostic/suite.coco +++ b/tests/src/cocotest/agnostic/suite.coco @@ -313,22 +313,31 @@ def suite_test(): assert container_(1) != container_(2) assert not container_(1) == container_(2) t = Tuple(1, 2) + assert repr(t) == "Tuple(elems=(1, 2))" assert t.elems == (1, 2) + assert isinstance(t.elems, tuple) assert t |> fmap$(-> _+1) == Tuple(2, 3) Tuple(x, y) = t assert x == 1 and y == 2 p = Pred("name", 1, 2) + assert repr(p) == "Pred(name='name', args=(1, 2))" assert p.name == "name" assert p.args == (1, 2) + assert isinstance(p.args, tuple) Pred(name, *args) = p assert name == "name" assert args == (1, 2) - p = Quant("name", "var", 1, 2) - assert p.name == "name" - assert p.var == "var" - assert p.args == (1, 2) - Pred(name, var, *args) = p + q = Quant("name", "var", 1, 2) + assert repr(q) == "Quant(name='name', var='var', args=(1, 2))" + assert q.name == "name" + assert q.var == "var" + assert q.args == (1, 2) + assert isinstance(q.args, tuple) + Pred(name, var, *args) = q assert name == "name" assert var == "var" assert args == (1, 2) + assert Pred(0, 1, 2) |> fmap$(-> _+1) == Pred(1, 2, 3) + assert Quant(0, 1, 2) |> fmap$(-> _+1) == Quant(0, 1, 2) + assert Tuple(0, 1, 2) != Pred(0, 1, 2) != Quant(0, 1, 2) != Tuple(0, 1, 2) return True From 231f536b6021c42aaca424a4c1956208961423d5 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Thu, 23 Mar 2017 13:29:02 -0700 Subject: [PATCH 104/176] Add a missing parenthesis --- coconut/compiler/grammar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coconut/compiler/grammar.py b/coconut/compiler/grammar.py index 175c429b4..c1a92ec4a 100644 --- a/coconut/compiler/grammar.py +++ b/coconut/compiler/grammar.py @@ -414,7 +414,7 @@ def {starred_arg}(self): def _make(cls, iterable, new=_coconut.tuple.__new__, len=None): {oind}return new(cls, iterable) {cind}def _asdict(self): - {oind}return _coconut.collections.OrderedDict([("{arg}", self[:])] + {oind}return _coconut.collections.OrderedDict([("{arg}", self[:])]) {cind}def __repr__(self): {oind}return "{name}(*{arg}=%r)" % (self[:],) {cind}def _replace(_self, **kwds): From ea38d08245a206af511c65b04db7b39c8ada0e3e Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Thu, 23 Mar 2017 13:40:30 -0700 Subject: [PATCH 105/176] Fix repr tests --- tests/src/cocotest/agnostic/suite.coco | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/src/cocotest/agnostic/suite.coco b/tests/src/cocotest/agnostic/suite.coco index 01a52a103..f5d347987 100644 --- a/tests/src/cocotest/agnostic/suite.coco +++ b/tests/src/cocotest/agnostic/suite.coco @@ -313,14 +313,14 @@ def suite_test(): assert container_(1) != container_(2) assert not container_(1) == container_(2) t = Tuple(1, 2) - assert repr(t) == "Tuple(elems=(1, 2))" + assert repr(t) == "Tuple(*elems=(1, 2))" assert t.elems == (1, 2) assert isinstance(t.elems, tuple) assert t |> fmap$(-> _+1) == Tuple(2, 3) Tuple(x, y) = t assert x == 1 and y == 2 p = Pred("name", 1, 2) - assert repr(p) == "Pred(name='name', args=(1, 2))" + assert repr(p) == "Pred(name='name', *args=(1, 2))" assert p.name == "name" assert p.args == (1, 2) assert isinstance(p.args, tuple) @@ -328,7 +328,7 @@ def suite_test(): assert name == "name" assert args == (1, 2) q = Quant("name", "var", 1, 2) - assert repr(q) == "Quant(name='name', var='var', args=(1, 2))" + assert repr(q) == "Quant(name='name', var='var', *args=(1, 2))" assert q.name == "name" assert q.var == "var" assert q.args == (1, 2) From ba0bfa1c25d13fad5cb26c3bb0f20e2d196f5ccf Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Thu, 23 Mar 2017 14:07:13 -0700 Subject: [PATCH 106/176] Fix data repr, bump version --- coconut/compiler/grammar.py | 2 +- coconut/root.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/coconut/compiler/grammar.py b/coconut/compiler/grammar.py index c1a92ec4a..dbbd43e3a 100644 --- a/coconut/compiler/grammar.py +++ b/coconut/compiler/grammar.py @@ -400,7 +400,7 @@ def {starred_arg}(self): oind=openindent, cind=closeindent, name=name, - args_for_repr=", ".join(arg + "={" + arg.lstrip("*") + "}" for arg in args), + args_for_repr=", ".join(arg + "={" + arg.lstrip("*") + "!r}" for arg in args), starred_arg=starred_arg, all_args=", ".join(args), num_base_args=str(len(base_args)), diff --git a/coconut/root.py b/coconut/root.py index f2af17c54..680cbbf96 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -26,7 +26,7 @@ VERSION = "1.2.2" VERSION_NAME = "Colonel" # False for release, int >= 1 for develop -DEVELOP = 8 +DEVELOP = 9 #----------------------------------------------------------------------------------------------------------------------- # CONSTANTS: From 32674680a1361e83da131795e8a5099e061a439c Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Thu, 23 Mar 2017 14:28:20 -0700 Subject: [PATCH 107/176] Fix incorrect test --- tests/src/cocotest/agnostic/suite.coco | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/cocotest/agnostic/suite.coco b/tests/src/cocotest/agnostic/suite.coco index f5d347987..1d136f11f 100644 --- a/tests/src/cocotest/agnostic/suite.coco +++ b/tests/src/cocotest/agnostic/suite.coco @@ -333,7 +333,7 @@ def suite_test(): assert q.var == "var" assert q.args == (1, 2) assert isinstance(q.args, tuple) - Pred(name, var, *args) = q + Quant(name, var, *args) = q assert name == "name" assert var == "var" assert args == (1, 2) From 271e388e70ad2ba5f2926fda9f70ed00c60cfe60 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Thu, 23 Mar 2017 15:07:23 -0700 Subject: [PATCH 108/176] Fix another incorrect test --- tests/src/cocotest/agnostic/suite.coco | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/cocotest/agnostic/suite.coco b/tests/src/cocotest/agnostic/suite.coco index 1d136f11f..98b8f041c 100644 --- a/tests/src/cocotest/agnostic/suite.coco +++ b/tests/src/cocotest/agnostic/suite.coco @@ -338,6 +338,6 @@ def suite_test(): assert var == "var" assert args == (1, 2) assert Pred(0, 1, 2) |> fmap$(-> _+1) == Pred(1, 2, 3) - assert Quant(0, 1, 2) |> fmap$(-> _+1) == Quant(0, 1, 2) + assert Quant(0, 1, 2) |> fmap$(-> _+1) == Quant(1, 2, 3) assert Tuple(0, 1, 2) != Pred(0, 1, 2) != Quant(0, 1, 2) != Tuple(0, 1, 2) return True From e185527d417080229f3f30c2721f4018f96e86d6 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Thu, 23 Mar 2017 15:43:24 -0700 Subject: [PATCH 109/176] Remove different data inequality test --- tests/src/cocotest/agnostic/suite.coco | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/src/cocotest/agnostic/suite.coco b/tests/src/cocotest/agnostic/suite.coco index 98b8f041c..a0dfbb895 100644 --- a/tests/src/cocotest/agnostic/suite.coco +++ b/tests/src/cocotest/agnostic/suite.coco @@ -339,5 +339,4 @@ def suite_test(): assert args == (1, 2) assert Pred(0, 1, 2) |> fmap$(-> _+1) == Pred(1, 2, 3) assert Quant(0, 1, 2) |> fmap$(-> _+1) == Quant(1, 2, 3) - assert Tuple(0, 1, 2) != Pred(0, 1, 2) != Quant(0, 1, 2) != Tuple(0, 1, 2) return True From 19aaa25d412ae4e4c420b323f4b8cb39f75f9a4d Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Thu, 23 Mar 2017 20:18:44 -0700 Subject: [PATCH 110/176] Allow different data representations --- tests/src/cocotest/agnostic/suite.coco | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/src/cocotest/agnostic/suite.coco b/tests/src/cocotest/agnostic/suite.coco index a0dfbb895..b99c35bad 100644 --- a/tests/src/cocotest/agnostic/suite.coco +++ b/tests/src/cocotest/agnostic/suite.coco @@ -320,7 +320,7 @@ def suite_test(): Tuple(x, y) = t assert x == 1 and y == 2 p = Pred("name", 1, 2) - assert repr(p) == "Pred(name='name', *args=(1, 2))" + assert repr(p) in ("Pred(name='name', *args=(1, 2))", "Pred(name=u'name', *args=(1, 2))") assert p.name == "name" assert p.args == (1, 2) assert isinstance(p.args, tuple) @@ -328,7 +328,7 @@ def suite_test(): assert name == "name" assert args == (1, 2) q = Quant("name", "var", 1, 2) - assert repr(q) == "Quant(name='name', var='var', *args=(1, 2))" + assert repr(q) in ("Quant(name='name', var='var', *args=(1, 2))", "Quant(name=u'name', var='var', *args=(1, 2))") assert q.name == "name" assert q.var == "var" assert q.args == (1, 2) From ece0e3de8bc7eff09157dca72e64b848db70acd3 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Thu, 23 Mar 2017 21:17:13 -0700 Subject: [PATCH 111/176] Improve clarity, fix oversight --- coconut/command/util.py | 2 +- coconut/compiler/matching.py | 2 +- tests/src/cocotest/agnostic/suite.coco | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/coconut/command/util.py b/coconut/command/util.py index ec3a4623b..37af0c269 100644 --- a/coconut/command/util.py +++ b/coconut/command/util.py @@ -323,7 +323,7 @@ class Runner(object): def __init__(self, comp=None, exit=None, store=False, path=None): """Creates the executor.""" - self.exit = sys.exit if exit is None else exit + self.exit = exit if exit is not None else sys.exit self.vars = self.build_vars(path) self.stored = [] if store else None if comp is not None: diff --git a/coconut/compiler/matching.py b/coconut/compiler/matching.py index 6d0edc232..59266698a 100644 --- a/coconut/compiler/matching.py +++ b/coconut/compiler/matching.py @@ -102,7 +102,7 @@ def __init__(self, loc, checkdefs=None, names=None, var_index=0): for checks, defs in checkdefs: self.checkdefs.append((checks[:], defs[:])) self.set_position(-1) - self.names = {} if names is None else names + self.names = names if names is not None else {} self.var_index = var_index self.others = [] self.guards = [] diff --git a/tests/src/cocotest/agnostic/suite.coco b/tests/src/cocotest/agnostic/suite.coco index b99c35bad..531a1c673 100644 --- a/tests/src/cocotest/agnostic/suite.coco +++ b/tests/src/cocotest/agnostic/suite.coco @@ -328,7 +328,7 @@ def suite_test(): assert name == "name" assert args == (1, 2) q = Quant("name", "var", 1, 2) - assert repr(q) in ("Quant(name='name', var='var', *args=(1, 2))", "Quant(name=u'name', var='var', *args=(1, 2))") + assert repr(q) in ("Quant(name='name', var='var', *args=(1, 2))", "Quant(name=u'name', var=u'var', *args=(1, 2))") assert q.name == "name" assert q.var == "var" assert q.args == (1, 2) From db1039e594cfcd1460175e0b0dca866256840b22 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Fri, 24 Mar 2017 00:25:18 -0700 Subject: [PATCH 112/176] Further improve Python 2 object --- coconut/icoconut/root.py | 8 ++++---- coconut/root.py | 29 ++++++++++++++--------------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/coconut/icoconut/root.py b/coconut/icoconut/root.py index defac3fd5..5742cbf23 100644 --- a/coconut/icoconut/root.py +++ b/coconut/icoconut/root.py @@ -78,7 +78,7 @@ def memoized_parse_sys(code): #----------------------------------------------------------------------------------------------------------------------- -class CoconutCompiler(CachingCompiler, object): +class CoconutCompiler(CachingCompiler): """IPython compiler for Coconut.""" def ast_parse(self, source, *args, **kwargs): @@ -100,7 +100,7 @@ def cache(self, code, *args, **kwargs): return super(CoconutCompiler, self).cache(compiled, *args, **kwargs) -class CoconutSplitter(IPythonInputSplitter, object): +class CoconutSplitter(IPythonInputSplitter): """IPython splitter for Coconut.""" def __init__(self, *args, **kwargs): @@ -127,7 +127,7 @@ def _coconut_compile(self, source, *args, **kwargs): return True -class CoconutShell(ZMQInteractiveShell, object): +class CoconutShell(ZMQInteractiveShell): """IPython shell for Coconut.""" input_splitter = CoconutSplitter(line_input_checker=True) input_transformer_manager = CoconutSplitter(line_input_checker=False) @@ -160,7 +160,7 @@ def user_expressions(self, expressions): InteractiveShellABC.register(CoconutShell) -class CoconutKernel(IPythonKernel, object): +class CoconutKernel(IPythonKernel): """Jupyter kernel for Coconut.""" shell_class = CoconutShell implementation = "icoconut" diff --git a/coconut/root.py b/coconut/root.py index 680cbbf96..d9e64935c 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -57,21 +57,7 @@ def __ne__(self, other): return eq else: return not eq -class object(_coconut_base_object): - __slots__ = () - if hasattr(_coconut_object, "__doc__"): - __doc__ = _coconut_object.__doc__ - class __metaclass__(type): - def __new__(cls, name, bases, dict): - if dict.get("__metaclass__") is not cls: - cls, bases = type, tuple(b if b is not _coconut_new_object else _coconut_base_object for b in bases) - return type.__new__(cls, name, bases, dict) - def __instancecheck__(cls, inst): - return _coconut.isinstance(inst, _coconut_object) - def __subclasscheck__(cls, subcls): - return _coconut.issubclass(subcls, _coconut_object) -_coconut_new_object = object -class range(object): +class range(_coconut_base_object): __slots__ = ("_xrange",) if hasattr(_coconut_xrange, "__doc__"): __doc__ = _coconut_xrange.__doc__ @@ -115,6 +101,19 @@ def __copy__(self): return self.__class__(*self._args) def __eq__(self, other): return _coconut.isinstance(other, self.__class__) and self._args == other._args +class object(_coconut_base_object): + __slots__ = () + if hasattr(_coconut_object, "__doc__"): + __doc__ = _coconut_object.__doc__ + class __metaclass__(type): + def __new__(cls, name, bases, dict): + if dict.get("__metaclass__") is not cls: + cls, bases = type, tuple(b if b is not _coconut.object else _coconut_base_object for b in bases) + return type.__new__(cls, name, bases, dict) + def __instancecheck__(cls, inst): + return _coconut.isinstance(inst, _coconut_object) + def __subclasscheck__(cls, subcls): + return _coconut.issubclass(subcls, _coconut_object) from collections import Sequence as _coconut_Sequence _coconut_Sequence.register(range) class int(_coconut_int): From 83e7262b60e476d982d1ce4a8ddbe0e4fdcc537f Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Fri, 24 Mar 2017 11:04:53 -0700 Subject: [PATCH 113/176] Removes Python 2 object metaclass --- coconut/root.py | 19 +++---------------- tests/src/cocotest/agnostic/main.coco | 2 -- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/coconut/root.py b/coconut/root.py index d9e64935c..9b38cbedf 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -45,11 +45,11 @@ PY3_HEADER = r'''py_chr, py_filter, py_hex, py_input, py_int, py_map, py_object, py_oct, py_open, py_print, py_range, py_str, py_zip, py_filter, py_reversed, py_enumerate = chr, filter, hex, input, int, map, object, oct, open, print, range, str, zip, filter, reversed, enumerate ''' PY27_HEADER = PY3_HEADER + r'''py_raw_input, py_xrange = raw_input, xrange -_coconut_NotImplemented, _coconut_raw_input, _coconut_xrange, _coconut_int, _coconut_long, _coconut_print, _coconut_str, _coconut_unicode, _coconut_repr, _coconut_object = NotImplemented, raw_input, xrange, int, long, print, str, unicode, repr, object +_coconut_NotImplemented, _coconut_raw_input, _coconut_xrange, _coconut_int, _coconut_long, _coconut_print, _coconut_str, _coconut_unicode, _coconut_repr = NotImplemented, raw_input, xrange, int, long, print, str, unicode, repr from future_builtins import * chr, str = unichr, unicode from io import open -class _coconut_base_object(_coconut_object): +class object(object): __slots__ = () def __ne__(self, other): eq = self == other @@ -57,7 +57,7 @@ def __ne__(self, other): return eq else: return not eq -class range(_coconut_base_object): +class range(object): __slots__ = ("_xrange",) if hasattr(_coconut_xrange, "__doc__"): __doc__ = _coconut_xrange.__doc__ @@ -101,19 +101,6 @@ def __copy__(self): return self.__class__(*self._args) def __eq__(self, other): return _coconut.isinstance(other, self.__class__) and self._args == other._args -class object(_coconut_base_object): - __slots__ = () - if hasattr(_coconut_object, "__doc__"): - __doc__ = _coconut_object.__doc__ - class __metaclass__(type): - def __new__(cls, name, bases, dict): - if dict.get("__metaclass__") is not cls: - cls, bases = type, tuple(b if b is not _coconut.object else _coconut_base_object for b in bases) - return type.__new__(cls, name, bases, dict) - def __instancecheck__(cls, inst): - return _coconut.isinstance(inst, _coconut_object) - def __subclasscheck__(cls, subcls): - return _coconut.issubclass(subcls, _coconut_object) from collections import Sequence as _coconut_Sequence _coconut_Sequence.register(range) class int(_coconut_int): diff --git a/tests/src/cocotest/agnostic/main.coco b/tests/src/cocotest/agnostic/main.coco index b70deaf6b..025f1c5a3 100644 --- a/tests/src/cocotest/agnostic/main.coco +++ b/tests/src/cocotest/agnostic/main.coco @@ -325,14 +325,12 @@ def main_test(): assert issubclass(int, py_int) class pyobjsub(py_object) class objsub(object) - assert issubclass(pyobjsub, object) assert not issubclass(pyobjsub, objsub) assert issubclass(objsub, object) assert issubclass(objsub, py_object) assert not issubclass(objsub, pyobjsub) pos = pyobjsub() os = objsub() - assert isinstance(pos, object) assert not isinstance(pos, objsub) assert isinstance(os, objsub) assert isinstance(os, object) From 3fbcce30c97901184c37848c7078a9112b50e7fb Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Fri, 24 Mar 2017 12:18:55 -0700 Subject: [PATCH 114/176] Fix an old incorrect test --- tests/src/cocotest/agnostic/main.coco | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/cocotest/agnostic/main.coco b/tests/src/cocotest/agnostic/main.coco index 025f1c5a3..e59f1764a 100644 --- a/tests/src/cocotest/agnostic/main.coco +++ b/tests/src/cocotest/agnostic/main.coco @@ -248,7 +248,7 @@ def main_test(): data abc(xyz) assert abc(10).xyz == 10 class aclass - assert isinstance(aclass, object) + assert isinstance(aclass(), object) assert tee((1,2)) |*> (is) assert tee(f{1,2}) |*> (is) assert (x -> 2 / x)(4) == 1/2 From 929a44e1c19b275310a5eac9048ca066eff89d15 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Fri, 24 Mar 2017 12:31:23 -0700 Subject: [PATCH 115/176] Adds back icoconut object inheritance --- coconut/icoconut/root.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/coconut/icoconut/root.py b/coconut/icoconut/root.py index 5742cbf23..defac3fd5 100644 --- a/coconut/icoconut/root.py +++ b/coconut/icoconut/root.py @@ -78,7 +78,7 @@ def memoized_parse_sys(code): #----------------------------------------------------------------------------------------------------------------------- -class CoconutCompiler(CachingCompiler): +class CoconutCompiler(CachingCompiler, object): """IPython compiler for Coconut.""" def ast_parse(self, source, *args, **kwargs): @@ -100,7 +100,7 @@ def cache(self, code, *args, **kwargs): return super(CoconutCompiler, self).cache(compiled, *args, **kwargs) -class CoconutSplitter(IPythonInputSplitter): +class CoconutSplitter(IPythonInputSplitter, object): """IPython splitter for Coconut.""" def __init__(self, *args, **kwargs): @@ -127,7 +127,7 @@ def _coconut_compile(self, source, *args, **kwargs): return True -class CoconutShell(ZMQInteractiveShell): +class CoconutShell(ZMQInteractiveShell, object): """IPython shell for Coconut.""" input_splitter = CoconutSplitter(line_input_checker=True) input_transformer_manager = CoconutSplitter(line_input_checker=False) @@ -160,7 +160,7 @@ def user_expressions(self, expressions): InteractiveShellABC.register(CoconutShell) -class CoconutKernel(IPythonKernel): +class CoconutKernel(IPythonKernel, object): """Jupyter kernel for Coconut.""" shell_class = CoconutShell implementation = "icoconut" From fe0881be2496964b5ab63bad45511f498147e810 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Fri, 24 Mar 2017 16:29:55 -0700 Subject: [PATCH 116/176] Use dict for OrderedDict on Python 2.6 --- coconut/compiler/header.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/coconut/compiler/header.py b/coconut/compiler/header.py index 7fa5e37a6..ac8c2f105 100644 --- a/coconut/compiler/header.py +++ b/coconut/compiler/header.py @@ -134,6 +134,13 @@ class _coconut:''' class _coconut(object):''' header += r''' import collections, functools, imp, itertools, operator, types, copy, pickle +''' + if not target: + header += r''' if _coconut_sys.version_info < (2, 7): + collections.OrderedDict = dict +''' + elif get_target_info(target) < (2, 7): + header += r''' collections.OrderedDict = dict ''' if target.startswith("2"): header += r''' abc = collections''' From 52b64c809b72eb21ba1f63c07f0cc8589a06f6e8 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Fri, 24 Mar 2017 18:46:34 -0700 Subject: [PATCH 117/176] Improve Python 2.6 OrderedDict support --- coconut/compiler/grammar.py | 4 ++-- coconut/compiler/header.py | 13 +++++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/coconut/compiler/grammar.py b/coconut/compiler/grammar.py index dbbd43e3a..41c6ec69f 100644 --- a/coconut/compiler/grammar.py +++ b/coconut/compiler/grammar.py @@ -385,7 +385,7 @@ def _make(cls, iterable, new=_coconut.tuple.__new__, len=_coconut.len): {oind}raise _coconut.TypeError("Expected at least 2 arguments, got %d" % len(result)) {cind}return result {cind}def _asdict(self): - {oind}return _coconut.collections.OrderedDict((f, _coconut.getattr(self, f)) for f in self._fields) + {oind}return _coconut.OrderedDict((f, _coconut.getattr(self, f)) for f in self._fields) {cind}def __repr__(self): {oind}return "{name}({args_for_repr})".format(**self._asdict()) {cind}def _replace(_self, **kwds): @@ -414,7 +414,7 @@ def {starred_arg}(self): def _make(cls, iterable, new=_coconut.tuple.__new__, len=None): {oind}return new(cls, iterable) {cind}def _asdict(self): - {oind}return _coconut.collections.OrderedDict([("{arg}", self[:])]) + {oind}return _coconut.OrderedDict([("{arg}", self[:])]) {cind}def __repr__(self): {oind}return "{name}(*{arg}=%r)" % (self[:],) {cind}def _replace(_self, **kwds): diff --git a/coconut/compiler/header.py b/coconut/compiler/header.py index ac8c2f105..5a64615bd 100644 --- a/coconut/compiler/header.py +++ b/coconut/compiler/header.py @@ -136,11 +136,16 @@ class _coconut(object):''' import collections, functools, imp, itertools, operator, types, copy, pickle ''' if not target: - header += r''' if _coconut_sys.version_info < (2, 7): - collections.OrderedDict = dict + header += r''' if _coconut_sys.version_info >= (2, 7): + OrderedDict = collections.OrderedDict + else: + OrderedDict = dict +''' + elif get_target_info(target) >= (2, 7): + header += r''' OrderedDict = collections.OrderedDict ''' - elif get_target_info(target) < (2, 7): - header += r''' collections.OrderedDict = dict + else: + header += r''' OrderedDict = dict ''' if target.startswith("2"): header += r''' abc = collections''' From cf0539dad17c51e91914db8bc6dd1e57eead5866 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 25 Mar 2017 13:43:43 -0700 Subject: [PATCH 118/176] Bump develop version --- coconut/root.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coconut/root.py b/coconut/root.py index 9b38cbedf..2e22b438c 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -26,7 +26,7 @@ VERSION = "1.2.2" VERSION_NAME = "Colonel" # False for release, int >= 1 for develop -DEVELOP = 9 +DEVELOP = 10 #----------------------------------------------------------------------------------------------------------------------- # CONSTANTS: From 1408bd088c46e1f2a89a3a56f3b823ffe5e0f663 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sun, 26 Mar 2017 00:45:33 -0700 Subject: [PATCH 119/176] Document starred data, inherit from object in data --- DOCS.md | 2 + HELP.md | 80 ++++++++-------- coconut/compiler/compiler.py | 108 +++++++++++++++++++++- coconut/compiler/grammar.py | 102 +------------------- tests/src/cocotest/agnostic/main.coco | 3 + tests/src/cocotest/agnostic/tutorial.coco | 64 ++++++------- 6 files changed, 186 insertions(+), 173 deletions(-) diff --git a/DOCS.md b/DOCS.md index a79a4a3eb..054f78414 100644 --- a/DOCS.md +++ b/DOCS.md @@ -492,6 +492,8 @@ data (): ``` `` is the name of the new data type, `` are the arguments to its constructor as well as the names of its attributes, and `` contains the data type's methods. +In addition to supporting standard `collections.namedtuple` subclassing when `` is a list of names, Coconut also supports an extended version where `` can contain a starred argument to collect extra parameters. + Subclassing `data` types can be done easily by inheriting from them in a normal Python `class`, although to make the new subclass immutable, the line ```coconut __slots__ = () diff --git a/HELP.md b/HELP.md index 364e7f42c..670f43ffe 100644 --- a/HELP.md +++ b/HELP.md @@ -523,23 +523,25 @@ One other thing to call attention to here is the use of `fmap`. `fmap` is a Coco Now that we've got the 2-vector under our belt, let's move to back to our original, more complicated problem: n-vectors, that is, vectors of arbitrary length. We're going to try to make our n-vector support all the basic vector operations, but we'll start out with just the `data` definition and the constructor: ```coconut -data vector(pts): +data vector(*pts): """Immutable n-vector.""" def __new__(cls, *pts): """Create a new vector from the given pts.""" - if len(pts) == 1 and pts[0] `isinstance` vector: - return pts[0] # vector(v) where v is a vector should return v + match (v is vector,) in pts: + return v # vector(v) where v is a vector should return v else: - return pts |> tuple |> datamaker(cls) # accesses base constructor + return pts |*> datamaker(cls) # accesses base constructor # Test cases: -vector(1, 2, 3) |> print # vector(pts=(1, 2, 3)) -vector(4, 5) |> vector |> print # vector(pts=(4, 5)) +vector(1, 2, 3) |> print # vector(*pts=(1, 2, 3)) +vector(4, 5) |> vector |> print # vector(*pts=(4, 5)) ``` -Copy, paste! The big new thing here is how to write `data` constructors. Since `data` types are immutable, `__init__` construction won't work. Instead, a different special method `__new__` is used, which must return the newly constructed instance, and unlike most methods, takes the class not the object as the first argument. Since `__new__` needs to return a fully constructed instance, in almost all cases access to the underlying `data` constructor will be necessary. To achieve this, Coconut provides the built-in function `datamaker`, which takes a data type, often the first argument to `__new__`, and returns its underlying `data` constructor. +Copy, paste! The big new thing here is how to write `data` constructors. Since `data` types are immutable, `__init__` construction won't work. Instead, a different special method `__new__` is used, which must return the newly constructed instance, and unlike most methods, takes the class not the object as the first argument. Since `__new__` needs to return a fully constructed instance, in almost all cases it will be necessary to access the underlying `data` constructor. To achieve this, Coconut provides the built-in function `datamaker`, which takes a data type, often the first argument to `__new__`, and returns its underlying `data` constructor. -In this case, the constructor checks whether nothing but another `vector` was passed, in which case it returns that, otherwise it returns the result of creating a tuple of the arguments and passing that to the underlying constructor, the form of which is `vector(pts)`, thus assigning the tuple to the `pts` attribute. +In this case, the constructor checks whether nothing but another `vector` was passed, in which case it returns that, otherwise it returns the result of creating a tuple of the arguments and passing that to the underlying constructor, the form of which is `vector(*pts)`, since that is how we declared the data type. + +The other new construct used here is the `|*>`, or star-pipe, operator, which functions exactly like the normal pipe, except that instead of calling the function with one argument, it calls it with as many arguments as there are elements in the sequence passed into it. The difference between `|*>` and `|>` is exactly analogous to the difference between `f(args)` and `f(*args)`. ### n-Vector Methods @@ -562,8 +564,6 @@ Next up is vector addition. The goal here is to add two vectors of equal length There are a couple of new constructs here, but the main notable one is the destructuring assignment statement `vector(other_pts) = other` which showcases the syntax for pattern-matching against data types: it mimics exactly the original `data` declaration of that data type. In this case, `vector(other_pts) = other` will only match a vector, raising a `MatchError` otherwise, and if it does match a vector, will assign the vector's `pts` attribute to the variable `other_pts`. -The other new construct used here is the `|*>`, or star-pipe, operator, which functions exactly like the normal pipe, except that instead of calling the function with one argument, it calls it with as many arguments as there are elements in the sequence passed into it. The difference between `|*>` and `|>` is exactly analogous to the difference between `f(args)` and `f(*args)`. - Next is vector subtraction, which is just like vector addition, but with `(-)` instead of `(+)`: ```coconut def __sub__(self, other) = @@ -610,14 +610,14 @@ The first thing to note here is that unlike with addition and subtraction, where Finally, putting everything together: ```coconut -data vector(pts): +data vector(*pts): """Immutable n-vector.""" def __new__(cls, *pts): """Create a new vector from the given pts.""" - if len(pts) == 1 and pts[0] `isinstance` vector: - return pts[0] # vector(v) where v is a vector should return v + match (v is vector,) in pts: + return v # vector(v) where v is a vector should return v else: - return pts |> tuple |> datamaker(cls) # accesses base constructor + return pts |*> datamaker(cls) # accesses base constructor def __abs__(self) = """Return the magnitude of the vector.""" self.pts |> map$(pow$(?, 2)) |> sum |> pow$(?, 0.5) @@ -652,16 +652,16 @@ data vector(pts): self * other # Test cases: -vector(1, 2, 3) |> print # vector(pts=(1, 2, 3)) -vector(4, 5) |> vector |> print # vector(pts=(4, 5)) +vector(1, 2, 3) |> print # vector(*pts=(1, 2, 3)) +vector(4, 5) |> vector |> print # vector(*pts=(4, 5)) vector(3, 4) |> abs |> print # 5 -vector(1, 2) + vector(2, 3) |> print # vector(pts=(3, 5)) -vector(2, 2) - vector(0, 1) |> print # vector(pts=(2, 1)) --vector(1, 3) |> print # vector(pts=(-1, -3)) +vector(1, 2) + vector(2, 3) |> print # vector(*pts=(3, 5)) +vector(2, 2) - vector(0, 1) |> print # vector(*pts=(2, 1)) +-vector(1, 3) |> print # vector(*pts=(-1, -3)) (vector(1, 2) == "string") |> print # False (vector(1, 2) == vector(3, 4)) |> print # False (vector(2, 4) == vector(2, 4)) |> print # True -2*vector(1, 2) |> print # vector(pts=(2, 4)) +2*vector(1, 2) |> print # vector(*pts=(2, 4)) vector(1, 2) * vector(1, 3) |> print # 7 ``` @@ -786,8 +786,8 @@ Now that we have a function that builds up all the points we need, it's time to Tests: ```coconut # You'll need to bring in the vector class from earlier to make these work -vector_field()$[0] |> print # vector(pts=(0, 0)) -vector_field()$[2:3] |> list |> print # [vector(pts=(1, 0))] +vector_field()$[0] |> print # vector(*pts=(0, 0)) +vector_field()$[2:3] |> list |> print # [vector(*pts=(1, 0))] ``` _Hint: Remember, the way we defined vector it takes the components as separate arguments, not a single tuple._ @@ -823,14 +823,14 @@ All we're doing is taking our `linearized_plane` and mapping `vector` over it, b Now that we've built all the functions we need for our vector field, it's time to put it all together and test it. Feel free to substitute in your versions of the functions below: ```coconut -data vector(pts): +data vector(*pts): """Immutable n-vector.""" def __new__(cls, *pts): """Create a new vector from the given pts.""" - if len(pts) == 1 and pts[0] `isinstance` vector: - return pts[0] # vector(v) where v is a vector should return v + match (v is vector,) in pts: + return v # vector(v) where v is a vector should return v else: - return pts |> tuple |> datamaker(cls) # accesses base constructor + return pts |*> datamaker(cls) # accesses base constructor def __abs__(self) = """Return the magnitude of the vector.""" self.pts |> map$(pow$(?, 2)) |> sum |> pow$(?, 0.5) @@ -874,8 +874,8 @@ diagonal_line(0) |> list |> print # [(0, 0)] diagonal_line(1) |> list |> print # [(0, 1), (1, 0)] linearized_plane()$[0] |> print # (0, 0) linearized_plane()$[:3] |> list |> print # [(0, 0), (0, 1), (1, 0)] -vector_field()$[0] |> print # vector(pts=(0, 0)) -vector_field()$[2:3] |> list |> print # [vector(pts=(1, 0))] +vector_field()$[0] |> print # vector(*pts=(0, 0)) +vector_field()$[2:3] |> list |> print # [vector(*pts=(1, 0))] ``` Copy, paste! Once you've made sure everything is working correctly if you substituted in your own functions, take a look at the last 4 tests. You'll notice that they use a new notation, similar to the notation for partial application we saw earlier, but with brackets instead of parentheses. This is the notation for iterator slicing. Similar to how partial application was lazy function calling, iterator slicing is _lazy sequence slicing_. Like with partial application, it is helpful to think of `$` as the _lazy-ify_ operator, in this case turning normal Python slicing, which is evaluated immediately, into lazy iterator slicing, which is evaluated only when the elements in the slice are needed. @@ -898,8 +898,8 @@ Vector division is just scalar division, so we're going to write a `__truediv__` Tests: ```coconut -vector(3, 4) / 1 |> print # vector(pts=(3.0, 4.0)) -vector(2, 4) / 2 |> print # vector(pts=(1.0, 2.0)) +vector(3, 4) / 1 |> print # vector(*pts=(3.0, 4.0)) +vector(2, 4) / 2 |> print # vector(*pts=(1.0, 2.0)) ``` _Hint: Look back at how we implemented scalar multiplication._ @@ -936,8 +936,8 @@ Next up, `.unit`. We're going to write a `unit` method that takes just `self` as Tests: ```coconut -vector(0, 1).unit() |> print # vector(pts=(0.0, 1.0)) -vector(5, 0).unit() |> print # vector(pts=(1.0, 0.0)) +vector(0, 1).unit() |> print # vector(*pts=(0.0, 1.0)) +vector(5, 0).unit() |> print # vector(*pts=(1.0, 0.0)) ```
@@ -1011,14 +1011,14 @@ And now it's time to put it all together. Feel free to substitute in your own ve ```coconut import math # necessary for math.acos in .angle -data vector(pts): +data vector(*pts): """Immutable n-vector.""" def __new__(cls, *pts): """Create a new vector from the given pts.""" - if len(pts) == 1 and pts[0] `isinstance` vector: - return pts[0] # vector(v) where v is a vector should return v + match (v is vector,) in pts: + return v # vector(v) where v is a vector should return v else: - return pts |> tuple |> datamaker(cls) # accesses base constructor + return pts |*> datamaker(cls) # accesses base constructor def __abs__(self) = """Return the magnitude of the vector.""" self.pts |> map$(pow$(?, 2)) |> sum |> pow$(?, 0.5) @@ -1057,10 +1057,10 @@ data vector(pts): def angle(self, other is vector) = math.acos(self.unit() * other.unit()) # Test cases: -vector(3, 4) / 1 |> print # vector(pts=(3.0, 4.0)) -vector(2, 4) / 2 |> print # vector(pts=(1.0, 2.0)) -vector(0, 1).unit() |> print # vector(pts=(0.0, 1.0)) -vector(5, 0).unit() |> print # vector(pts=(1.0, 0.0)) +vector(3, 4) / 1 |> print # vector(*pts=(3.0, 4.0)) +vector(2, 4) / 2 |> print # vector(*pts=(1.0, 2.0)) +vector(0, 1).unit() |> print # vector(*pts=(0.0, 1.0)) +vector(5, 0).unit() |> print # vector(*pts=(1.0, 0.0)) vector(2, 0).angle(vector(3, 0)) |> print # 0.0 print(vector(1, 0).angle(vector(0, 2)), math.pi/2) # should be the same vector(1, 2).angle(5) # MatchError diff --git a/coconut/compiler/compiler.py b/coconut/compiler/compiler.py index 573f97aba..b1b138235 100644 --- a/coconut/compiler/compiler.py +++ b/coconut/compiler/compiler.py @@ -290,6 +290,7 @@ def bind(self): self.endline <<= attach(self.endline_ref, self.endline_handle) self.moduledoc_item <<= trace(attach(self.moduledoc, self.set_docstring)) self.name <<= trace(attach(self.base_name, self.name_check)) + self.set_literal <<= trace(attach(self.set_literal_ref, self.set_literal_handle)) self.set_letter_literal <<= trace(attach(self.set_letter_literal_ref, self.set_letter_literal_handle)) self.classlist <<= trace(attach(self.classlist_ref, self.classlist_handle)) @@ -309,6 +310,8 @@ def bind(self): self.unsafe_typedef_default <<= trace(attach(self.unsafe_typedef_default_ref, self.unsafe_typedef_handle)) self.return_typedef <<= trace(attach(self.return_typedef_ref, self.typedef_handle)) self.typed_assign_stmt <<= trace(attach(self.typed_assign_stmt_ref, self.typed_assign_stmt_handle)) + self.datadef <<= trace(attach(self.datadef_ref, self.data_handle)) + self.u_string <<= attach(self.u_string_ref, self.u_string_check) self.f_string <<= attach(self.f_string_ref, self.f_string_check) self.matrix_at <<= attach(self.matrix_at_ref, self.matrix_at_check) @@ -454,7 +457,7 @@ def target_info_len2(self): return info[:2] def make_syntax_err(self, err, original): - """Handles CoconutDeferredSyntaxError.""" + """Make a CoconutSyntaxError from a CoconutDeferredSyntaxError.""" msg, loc = err.args return self.make_err(CoconutSyntaxError, msg, original, loc) @@ -1034,6 +1037,109 @@ def classlist_handle(self, original, loc, tokens): else: raise CoconutInternalException("invalid classlist tokens", tokens) + def data_handle(self, original, loc, tokens): + """Processes data blocks.""" + if len(tokens) == 3: + name, args, stmts = tokens + else: + raise CoconutInternalException("invalid data tokens", tokens) + base_args, starred_arg = [], None + for i, arg in enumerate(args): + if arg.startswith("_"): + raise CoconutDeferredSyntaxError("data fields cannot start with an underscore", loc) + elif arg.startswith("*"): + if i != len(args) - 1: + raise CoconutDeferredSyntaxError("starred data field must come last", loc) + starred_arg = arg[1:] + else: + base_args.append(arg) + attr_str = " ".join(base_args) + extra_stmts = "__slots__ = ()\n" + if starred_arg is not None: + attr_str += (" " if attr_str else "") + starred_arg + if base_args: + extra_stmts += r'''def __new__(_cls, {all_args}): + {oind}return _coconut.tuple.__new__(_cls, {base_args_tuple} + {starred_arg}) + {cind}@_coconut.classmethod + def _make(cls, iterable, new=_coconut.tuple.__new__, len=_coconut.len): + {oind}result = new(cls, iterable) + if len(result) < {num_base_args}: + {oind}raise _coconut.TypeError("Expected at least 2 arguments, got %d" % len(result)) + {cind}return result + {cind}def _asdict(self): + {oind}return _coconut.OrderedDict((f, _coconut.getattr(self, f)) for f in self._fields) + {cind}def __repr__(self): + {oind}return "{name}({args_for_repr})".format(**self._asdict()) + {cind}def _replace(_self, **kwds): + {oind}result = _self._make(_coconut.tuple(_coconut.map(kwds.pop, {quoted_base_args_tuple}, _self)) + kwds.pop("{starred_arg}", self.{starred_arg})) + if kwds: + {oind}raise _coconut.ValueError("Got unexpected field names: %r" % kwds.keys()) + {cind}return result + {cind}@_coconut.property + def {starred_arg}(self): + {oind}return self[{num_base_args}:] + {cind}'''.format( + oind=openindent, + cind=closeindent, + name=name, + args_for_repr=", ".join(arg + "={" + arg.lstrip("*") + "!r}" for arg in args), + starred_arg=starred_arg, + all_args=", ".join(args), + num_base_args=str(len(base_args)), + base_args_tuple="(" + ", ".join(base_args) + ("," if len(base_args) == 1 else "") + ")", + quoted_base_args_tuple='("' + '", "'.join(base_args) + '"' + ("," if len(base_args) == 1 else "") + ")", + ) + else: + extra_stmts += r'''def __new__(_cls, *{arg}): + {oind}return _coconut.tuple.__new__(_cls, {arg}) + {cind}@_coconut.classmethod + def _make(cls, iterable, new=_coconut.tuple.__new__, len=None): + {oind}return new(cls, iterable) + {cind}def _asdict(self): + {oind}return _coconut.OrderedDict([("{arg}", self[:])]) + {cind}def __repr__(self): + {oind}return "{name}(*{arg}=%r)" % (self[:],) + {cind}def _replace(_self, **kwds): + {oind}result = self._make(kwds.pop("{arg}", _self)) + if kwds: + {oind}raise _coconut.ValueError("Got unexpected field names: %r" % kwds.keys()) + {cind}return result + {cind}@_coconut.property + def {arg}(self): + {oind}return self[:] + {cind}'''.format( + oind=openindent, + cind=closeindent, + name=name, + arg=starred_arg, + ) + out = ( + "class " + name + "(" + '_coconut.collections.namedtuple("' + name + '", "' + attr_str + '")' + + (", _coconut.object" if not self.target.startswith("3") else "") + + "):\n" + openindent + ) + rest = None + if "simple" in stmts.keys() and len(stmts) == 1: + out += extra_stmts + rest = stmts[0] + elif "docstring" in stmts.keys() and len(stmts) == 1: + out += stmts[0] + extra_stmts + elif "complex" in stmts.keys() and len(stmts) == 1: + out += extra_stmts + rest = "".join(stmts[0]) + elif "complex" in stmts.keys() and len(stmts) == 2: + out += stmts[0] + extra_stmts + rest = "".join(stmts[1]) + elif "empty" in stmts.keys() and len(stmts) == 1: + out += extra_stmts.rstrip() + stmts[0] + else: + raise CoconutInternalException("invalid inner data tokens", stmts) + if rest is not None and rest != "pass\n": + out += rest + out += closeindent + return out + def import_handle(self, original, loc, tokens): """Universalizes imports.""" if len(tokens) == 1: diff --git a/coconut/compiler/grammar.py b/coconut/compiler/grammar.py index 41c6ec69f..55175b5c1 100644 --- a/coconut/compiler/grammar.py +++ b/coconut/compiler/grammar.py @@ -355,105 +355,6 @@ def math_funcdef_handle(tokens): raise CoconutInternalException("invalid assignment function definition tokens") -def data_handle(original, loc, tokens): - """Processes data blocks.""" - if len(tokens) == 3: - name, args, stmts = tokens - else: - raise CoconutInternalException("invalid data tokens", tokens) - base_args, starred_arg = [], None - for i, arg in enumerate(args): - if arg.startswith("_"): - raise CoconutDeferredSyntaxError("data fields cannot start with an underscore", loc) - elif arg.startswith("*"): - if i != len(args) - 1: - raise CoconutDeferredSyntaxError("starred data field must come last", loc) - starred_arg = arg[1:] - else: - base_args.append(arg) - attr_str = " ".join(base_args) - extra_stmts = "__slots__ = ()\n" - if starred_arg is not None: - attr_str += (" " if attr_str else "") + starred_arg - if base_args: - extra_stmts += r'''def __new__(_cls, {all_args}): - {oind}return _coconut.tuple.__new__(_cls, {base_args_tuple} + {starred_arg}) -{cind}@_coconut.classmethod -def _make(cls, iterable, new=_coconut.tuple.__new__, len=_coconut.len): - {oind}result = new(cls, iterable) - if len(result) < {num_base_args}: - {oind}raise _coconut.TypeError("Expected at least 2 arguments, got %d" % len(result)) - {cind}return result -{cind}def _asdict(self): - {oind}return _coconut.OrderedDict((f, _coconut.getattr(self, f)) for f in self._fields) -{cind}def __repr__(self): - {oind}return "{name}({args_for_repr})".format(**self._asdict()) -{cind}def _replace(_self, **kwds): - {oind}result = _self._make(_coconut.tuple(_coconut.map(kwds.pop, {quoted_base_args_tuple}, _self)) + kwds.pop("{starred_arg}", self.{starred_arg})) - if kwds: - {oind}raise _coconut.ValueError("Got unexpected field names: %r" % kwds.keys()) - {cind}return result -{cind}@_coconut.property -def {starred_arg}(self): - {oind}return self[{num_base_args}:] -{cind}'''.format( - oind=openindent, - cind=closeindent, - name=name, - args_for_repr=", ".join(arg + "={" + arg.lstrip("*") + "!r}" for arg in args), - starred_arg=starred_arg, - all_args=", ".join(args), - num_base_args=str(len(base_args)), - base_args_tuple="(" + ", ".join(base_args) + ("," if len(base_args) == 1 else "") + ")", - quoted_base_args_tuple='("' + '", "'.join(base_args) + '"' + ("," if len(base_args) == 1 else "") + ")", - ) - else: - extra_stmts += r'''def __new__(_cls, *{arg}): - {oind}return _coconut.tuple.__new__(_cls, {arg}) -{cind}@_coconut.classmethod -def _make(cls, iterable, new=_coconut.tuple.__new__, len=None): - {oind}return new(cls, iterable) -{cind}def _asdict(self): - {oind}return _coconut.OrderedDict([("{arg}", self[:])]) -{cind}def __repr__(self): - {oind}return "{name}(*{arg}=%r)" % (self[:],) -{cind}def _replace(_self, **kwds): - {oind}result = self._make(kwds.pop("{arg}", _self)) - if kwds: - {oind}raise _coconut.ValueError("Got unexpected field names: %r" % kwds.keys()) - {cind}return result -{cind}@_coconut.property -def {arg}(self): - {oind}return self[:] -{cind}'''.format( - oind=openindent, - cind=closeindent, - name=name, - arg=starred_arg, - ) - out = "class " + name + '(_coconut.collections.namedtuple("' + name + '", "' + attr_str + '")):\n' + openindent - rest = None - if "simple" in stmts.keys() and len(stmts) == 1: - out += extra_stmts - rest = stmts[0] - elif "docstring" in stmts.keys() and len(stmts) == 1: - out += stmts[0] + extra_stmts - elif "complex" in stmts.keys() and len(stmts) == 1: - out += extra_stmts - rest = "".join(stmts[0]) - elif "complex" in stmts.keys() and len(stmts) == 2: - out += stmts[0] + extra_stmts - rest = "".join(stmts[1]) - elif "empty" in stmts.keys() and len(stmts) == 1: - out += extra_stmts.rstrip() + stmts[0] - else: - raise CoconutInternalException("invalid inner data tokens", stmts) - if rest is not None and rest != "pass\n": - out += rest - out += closeindent - return out - - def decorator_handle(tokens): """Processes decorators.""" defs = [] @@ -1306,6 +1207,7 @@ class Grammar(object): + (def_match_funcdef | math_match_funcdef) ) + datadef = Forward() data_args = Group(Optional(lparen.suppress() + Optional( tokenlist(condense(Optional(star) + name), comma) ) + rparen.suppress())) @@ -1314,7 +1216,7 @@ class Grammar(object): | (newline.suppress() + indent.suppress() + docstring + dedent.suppress() | docstring)("docstring") | simple_stmt("simple") ) | newline("empty")) - datadef = condense(attach(Keyword("data").suppress() + name - data_args - data_suite, data_handle)) + datadef_ref = condense(attach(Keyword("data").suppress() + name - data_args - data_suite, data_handle)) simple_decorator = condense(dotted_name + Optional(function_call))("simple") complex_decorator = test("test") diff --git a/tests/src/cocotest/agnostic/main.coco b/tests/src/cocotest/agnostic/main.coco index e59f1764a..ea57c11a7 100644 --- a/tests/src/cocotest/agnostic/main.coco +++ b/tests/src/cocotest/agnostic/main.coco @@ -247,7 +247,10 @@ def main_test(): assert derp()() == 15 data abc(xyz) assert abc(10).xyz == 10 + assert issubclass(abc, object) + assert isinstance(abc(10), object) class aclass + assert issubclass(aclass, object) assert isinstance(aclass(), object) assert tee((1,2)) |*> (is) assert tee(f{1,2}) |*> (is) diff --git a/tests/src/cocotest/agnostic/tutorial.coco b/tests/src/cocotest/agnostic/tutorial.coco index cc13eec1e..1db0eeacb 100644 --- a/tests/src/cocotest/agnostic/tutorial.coco +++ b/tests/src/cocotest/agnostic/tutorial.coco @@ -229,27 +229,27 @@ except AttributeError: else: assert False -data vector(pts): +data vector(*pts): """Immutable n-vector.""" def __new__(cls, *pts): """Create a new vector from the given pts.""" - if len(pts) == 1 and pts[0] `isinstance` vector: - return pts[0] # vector(v) where v is a vector should return v + match (v is vector,) in pts: + return v # vector(v) where v is a vector should return v else: - return pts |> tuple |> datamaker(cls) # accesses base constructor + return pts |*> datamaker(cls) # accesses base constructor # Test cases: -assert vector(1, 2, 3) |> str == "vector(pts=(1, 2, 3))" -assert vector(4, 5) |> vector |> str == "vector(pts=(4, 5))" +assert vector(1, 2, 3) |> str == "vector(*pts=(1, 2, 3))" +assert vector(4, 5) |> vector |> str == "vector(*pts=(4, 5))" -data vector(pts): +data vector(*pts): """Immutable n-vector.""" def __new__(cls, *pts): """Create a new vector from the given pts.""" - if len(pts) == 1 and pts[0] `isinstance` vector: - return pts[0] # vector(v) where v is a vector should return v + match (v is vector,) in pts: + return v # vector(v) where v is a vector should return v else: - return pts |> tuple |> datamaker(cls) # accesses base constructor + return pts |*> datamaker(cls) # accesses base constructor def __abs__(self) = """Return the magnitude of the vector.""" self.pts |> map$(pow$(?, 2)) |> sum |> pow$(?, 0.5) @@ -284,16 +284,16 @@ data vector(pts): self * other # Test cases: -assert vector(1, 2, 3) |> str == "vector(pts=(1, 2, 3))" -assert vector(4, 5) |> vector |> str == "vector(pts=(4, 5))" +assert vector(1, 2, 3) |> str == "vector(*pts=(1, 2, 3))" +assert vector(4, 5) |> vector |> str == "vector(*pts=(4, 5))" assert vector(3, 4) |> abs == 5 -assert vector(1, 2) + vector(2, 3) |> str == "vector(pts=(3, 5))" -assert vector(2, 2) - vector(0, 1) |> str == "vector(pts=(2, 1))" -assert -vector(1, 3) |> str == "vector(pts=(-1, -3))" +assert vector(1, 2) + vector(2, 3) |> str == "vector(*pts=(3, 5))" +assert vector(2, 2) - vector(0, 1) |> str == "vector(*pts=(2, 1))" +assert -vector(1, 3) |> str == "vector(*pts=(-1, -3))" assert (vector(1, 2) == "string") is False assert (vector(1, 2) == vector(3, 4)) is False assert (vector(2, 4) == vector(2, 4)) is True -assert 2*vector(1, 2) |> str == "vector(pts=(2, 4))" +assert 2*vector(1, 2) |> str == "vector(*pts=(2, 4))" assert vector(1, 2) * vector(1, 3) == 7 def diagonal_line(n) = range(n+1) |> map$((i) -> (i, n-i)) @@ -313,17 +313,17 @@ assert linearized_plane()$[:3] |> list == [(0, 0), (0, 1), (1, 0)] def vector_field() = linearized_plane() |> map$((xy) -> vector(*xy)) # You'll need to bring in the vector class from earlier to make these work -assert vector_field()$[0] |> str == "vector(pts=(0, 0))" -assert vector_field()$[2:3] |> list |> str == "[vector(pts=(1, 0))]" +assert vector_field()$[0] |> str == "vector(*pts=(0, 0))" +assert vector_field()$[2:3] |> list |> str == "[vector(*pts=(1, 0))]" -data vector(pts): +data vector(*pts): """Immutable n-vector.""" def __new__(cls, *pts): """Create a new vector from the given pts.""" - if len(pts) == 1 and pts[0] `isinstance` vector: - return pts[0] # vector(v) where v is a vector should return v + match (v is vector,) in pts: + return v # vector(v) where v is a vector should return v else: - return pts |> tuple |> datamaker(cls) # accesses base constructor + return pts |*> datamaker(cls) # accesses base constructor def __abs__(self) = """Return the magnitude of the vector.""" self.pts |> map$(pow$(?, 2)) |> sum |> pow$(?, 0.5) @@ -367,19 +367,19 @@ assert diagonal_line(0) |> list == [(0, 0)] assert diagonal_line(1) |> list == [(0, 1), (1, 0)] assert linearized_plane()$[0] == (0, 0) assert linearized_plane()$[:3] |> list == [(0, 0), (0, 1), (1, 0)] -assert vector_field()$[0] |> str == "vector(pts=(0, 0))" -assert vector_field()$[2:3] |> list |> str == "[vector(pts=(1, 0))]" +assert vector_field()$[0] |> str == "vector(*pts=(0, 0))" +assert vector_field()$[2:3] |> list |> str == "[vector(*pts=(1, 0))]" import math # necessary for math.acos in .angle -data vector(pts): +data vector(*pts): """Immutable n-vector.""" def __new__(cls, *pts): """Create a new vector from the given pts.""" - if len(pts) == 1 and pts[0] `isinstance` vector: - return pts[0] # vector(v) where v is a vector should return v + match (v is vector,) in pts: + return v # vector(v) where v is a vector should return v else: - return pts |> tuple |> datamaker(cls) # accesses base constructor + return pts |*> datamaker(cls) # accesses base constructor def __abs__(self) = """Return the magnitude of the vector.""" self.pts |> map$(pow$(?, 2)) |> sum |> pow$(?, 0.5) @@ -418,10 +418,10 @@ data vector(pts): def angle(self, other is vector) = math.acos(self.unit() * other.unit()) # Test cases: -assert vector(3, 4) / 1 |> str == "vector(pts=(3.0, 4.0))" -assert vector(2, 4) / 2 |> str == "vector(pts=(1.0, 2.0))" -assert vector(0, 1).unit() |> str == "vector(pts=(0.0, 1.0))" -assert vector(5, 0).unit() |> str == "vector(pts=(1.0, 0.0))" +assert vector(3, 4) / 1 |> str == "vector(*pts=(3.0, 4.0))" +assert vector(2, 4) / 2 |> str == "vector(*pts=(1.0, 2.0))" +assert vector(0, 1).unit() |> str == "vector(*pts=(0.0, 1.0))" +assert vector(5, 0).unit() |> str == "vector(*pts=(1.0, 0.0))" assert vector(2, 0).angle(vector(3, 0)) == 0.0 assert vector(1, 0).angle(vector(0, 2)) == math.pi/2 try: From edfe409da0a912dca9a147aceb6490bd95f6351e Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sun, 26 Mar 2017 01:09:33 -0700 Subject: [PATCH 120/176] Fix improved data support --- HELP.md | 4 ++++ coconut/compiler/grammar.py | 2 +- tests/src/cocotest/agnostic/tutorial.coco | 3 +++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/HELP.md b/HELP.md index 670f43ffe..f43898820 100644 --- a/HELP.md +++ b/HELP.md @@ -588,6 +588,7 @@ Our next method will be equality. We're again going to use `data` pattern-matchi return True else: return False + def __ne__(self, other) = not self == other ``` The only new construct here is the use of `=self.pts` in the `match` statement. This construct is used to perform a check inside of the pattern-matching, making sure the `match` only succeeds if `other.pts == self.pts`. @@ -640,6 +641,7 @@ data vector(*pts): return True else: return False + def __ne__(self, other) = not self == other def __mul__(self, other): """Scalar multiplication and dot product.""" match vector(other_pts) in other: @@ -853,6 +855,7 @@ data vector(*pts): return True else: return False + def __ne__(self, other) = not self == other def __mul__(self, other): """Scalar multiplication and dot product.""" match vector(other_pts) in other: @@ -1041,6 +1044,7 @@ data vector(*pts): return True else: return False + def __ne__(self, other) = not self == other def __mul__(self, other): """Scalar multiplication and dot product.""" match vector(other_pts) in other: diff --git a/coconut/compiler/grammar.py b/coconut/compiler/grammar.py index 55175b5c1..b652a543a 100644 --- a/coconut/compiler/grammar.py +++ b/coconut/compiler/grammar.py @@ -1216,7 +1216,7 @@ class Grammar(object): | (newline.suppress() + indent.suppress() + docstring + dedent.suppress() | docstring)("docstring") | simple_stmt("simple") ) | newline("empty")) - datadef_ref = condense(attach(Keyword("data").suppress() + name - data_args - data_suite, data_handle)) + datadef_ref = Keyword("data").suppress() + name - data_args - data_suite simple_decorator = condense(dotted_name + Optional(function_call))("simple") complex_decorator = test("test") diff --git a/tests/src/cocotest/agnostic/tutorial.coco b/tests/src/cocotest/agnostic/tutorial.coco index 1db0eeacb..954ea6914 100644 --- a/tests/src/cocotest/agnostic/tutorial.coco +++ b/tests/src/cocotest/agnostic/tutorial.coco @@ -272,6 +272,7 @@ data vector(*pts): return True else: return False + def __ne__(self, other) = not self == other def __mul__(self, other): """Scalar multiplication and dot product.""" match vector(other_pts) in other: @@ -346,6 +347,7 @@ data vector(*pts): return True else: return False + def __ne__(self, other) = not self == other def __mul__(self, other): """Scalar multiplication and dot product.""" match vector(other_pts) in other: @@ -402,6 +404,7 @@ data vector(*pts): return True else: return False + def __ne__(self, other) = not self == other def __mul__(self, other): """Scalar multiplication and dot product.""" match vector(other_pts) in other: From 2d5eec67ccc1341c2f6c5110e55912705b335a21 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sun, 26 Mar 2017 10:54:28 -0700 Subject: [PATCH 121/176] Fix datamaker usage --- DOCS.md | 2 ++ HELP.md | 10 +++++----- tests/src/cocotest/agnostic/tutorial.coco | 8 ++++---- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/DOCS.md b/DOCS.md index 054f78414..7ac169065 100644 --- a/DOCS.md +++ b/DOCS.md @@ -1580,6 +1580,8 @@ _Can't be done quickly without Coconut's iterator slicing, which requires many c Coconut provides the `datamaker` function to allow direct access to the base constructor of data types created with the Coconut `data` statement. This is particularly useful when writing alternative constructors for data types by overwriting `__new__`. +For `data` objects, `datamaker` always returns a constructor that takes a single iterator, the values of which are then put into the fields of the data type. + For non-`data` objects, equivalent to: ```coconut def datamaker(data_type): diff --git a/HELP.md b/HELP.md index f43898820..880da72c7 100644 --- a/HELP.md +++ b/HELP.md @@ -530,7 +530,7 @@ data vector(*pts): match (v is vector,) in pts: return v # vector(v) where v is a vector should return v else: - return pts |*> datamaker(cls) # accesses base constructor + return pts |> datamaker(cls) # accesses base constructor # Test cases: vector(1, 2, 3) |> print # vector(*pts=(1, 2, 3)) @@ -539,7 +539,7 @@ vector(4, 5) |> vector |> print # vector(*pts=(4, 5)) Copy, paste! The big new thing here is how to write `data` constructors. Since `data` types are immutable, `__init__` construction won't work. Instead, a different special method `__new__` is used, which must return the newly constructed instance, and unlike most methods, takes the class not the object as the first argument. Since `__new__` needs to return a fully constructed instance, in almost all cases it will be necessary to access the underlying `data` constructor. To achieve this, Coconut provides the built-in function `datamaker`, which takes a data type, often the first argument to `__new__`, and returns its underlying `data` constructor. -In this case, the constructor checks whether nothing but another `vector` was passed, in which case it returns that, otherwise it returns the result of creating a tuple of the arguments and passing that to the underlying constructor, the form of which is `vector(*pts)`, since that is how we declared the data type. +In this case, the constructor checks whether nothing but another `vector` was passed, in which case it returns that, otherwise it returns the result of creating a tuple of the arguments and passing that to the underlying constructor, which takes an iterable and puts its values into the data type's fields. The other new construct used here is the `|*>`, or star-pipe, operator, which functions exactly like the normal pipe, except that instead of calling the function with one argument, it calls it with as many arguments as there are elements in the sequence passed into it. The difference between `|*>` and `|>` is exactly analogous to the difference between `f(args)` and `f(*args)`. @@ -618,7 +618,7 @@ data vector(*pts): match (v is vector,) in pts: return v # vector(v) where v is a vector should return v else: - return pts |*> datamaker(cls) # accesses base constructor + return pts |> datamaker(cls) # accesses base constructor def __abs__(self) = """Return the magnitude of the vector.""" self.pts |> map$(pow$(?, 2)) |> sum |> pow$(?, 0.5) @@ -832,7 +832,7 @@ data vector(*pts): match (v is vector,) in pts: return v # vector(v) where v is a vector should return v else: - return pts |*> datamaker(cls) # accesses base constructor + return pts |> datamaker(cls) # accesses base constructor def __abs__(self) = """Return the magnitude of the vector.""" self.pts |> map$(pow$(?, 2)) |> sum |> pow$(?, 0.5) @@ -1021,7 +1021,7 @@ data vector(*pts): match (v is vector,) in pts: return v # vector(v) where v is a vector should return v else: - return pts |*> datamaker(cls) # accesses base constructor + return pts |> datamaker(cls) # accesses base constructor def __abs__(self) = """Return the magnitude of the vector.""" self.pts |> map$(pow$(?, 2)) |> sum |> pow$(?, 0.5) diff --git a/tests/src/cocotest/agnostic/tutorial.coco b/tests/src/cocotest/agnostic/tutorial.coco index 954ea6914..868c2dafb 100644 --- a/tests/src/cocotest/agnostic/tutorial.coco +++ b/tests/src/cocotest/agnostic/tutorial.coco @@ -236,7 +236,7 @@ data vector(*pts): match (v is vector,) in pts: return v # vector(v) where v is a vector should return v else: - return pts |*> datamaker(cls) # accesses base constructor + return pts |> datamaker(cls) # accesses base constructor # Test cases: assert vector(1, 2, 3) |> str == "vector(*pts=(1, 2, 3))" @@ -249,7 +249,7 @@ data vector(*pts): match (v is vector,) in pts: return v # vector(v) where v is a vector should return v else: - return pts |*> datamaker(cls) # accesses base constructor + return pts |> datamaker(cls) # accesses base constructor def __abs__(self) = """Return the magnitude of the vector.""" self.pts |> map$(pow$(?, 2)) |> sum |> pow$(?, 0.5) @@ -324,7 +324,7 @@ data vector(*pts): match (v is vector,) in pts: return v # vector(v) where v is a vector should return v else: - return pts |*> datamaker(cls) # accesses base constructor + return pts |> datamaker(cls) # accesses base constructor def __abs__(self) = """Return the magnitude of the vector.""" self.pts |> map$(pow$(?, 2)) |> sum |> pow$(?, 0.5) @@ -381,7 +381,7 @@ data vector(*pts): match (v is vector,) in pts: return v # vector(v) where v is a vector should return v else: - return pts |*> datamaker(cls) # accesses base constructor + return pts |> datamaker(cls) # accesses base constructor def __abs__(self) = """Return the magnitude of the vector.""" self.pts |> map$(pow$(?, 2)) |> sum |> pow$(?, 0.5) From 86a8e9b7c44a88ed7510282f87e34c50dfc28037 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sun, 26 Mar 2017 15:54:16 -0700 Subject: [PATCH 122/176] Fix datamaker on starred data types --- coconut/compiler/header.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coconut/compiler/header.py b/coconut/compiler/header.py index 5a64615bd..aaa710010 100644 --- a/coconut/compiler/header.py +++ b/coconut/compiler/header.py @@ -532,7 +532,7 @@ def __repr__(self): return _coconut.repr(self.func) + "$(" + ", ".join(args) + ")" def datamaker(data_type): """Returns base data constructor of passed data type.""" - if _coconut.isinstance(data_type, _coconut.tuple) and _coconut.hasattr(data_type, "_make"): + if _coconut.issubclass(data_type, _coconut.tuple) and _coconut.hasattr(data_type, "_make"): return data_type._make else: return _coconut.functools.partial(_coconut.super(data_type, data_type).__new__, data_type) From 1a6623196e84f85bf268cfa05e3d7c176b2b91c2 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sun, 26 Mar 2017 15:56:03 -0700 Subject: [PATCH 123/176] Further improve datamaker --- coconut/compiler/header.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coconut/compiler/header.py b/coconut/compiler/header.py index aaa710010..e3227dab3 100644 --- a/coconut/compiler/header.py +++ b/coconut/compiler/header.py @@ -532,7 +532,7 @@ def __repr__(self): return _coconut.repr(self.func) + "$(" + ", ".join(args) + ")" def datamaker(data_type): """Returns base data constructor of passed data type.""" - if _coconut.issubclass(data_type, _coconut.tuple) and _coconut.hasattr(data_type, "_make"): + if _coconut.hasattr(data_type, "_make") and (_coconut.issubclass(data_type, _coconut.tuple) or _coconut.isinstance(data_type, _coconut.tuple)): return data_type._make else: return _coconut.functools.partial(_coconut.super(data_type, data_type).__new__, data_type) From ecfcf08dbb953b177d9228449c8a02ceb664397d Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 28 Mar 2017 12:07:52 -0700 Subject: [PATCH 124/176] Have _make support keyword-only args on target 3 --- coconut/compiler/compiler.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/coconut/compiler/compiler.py b/coconut/compiler/compiler.py index b1b138235..0d257adb7 100644 --- a/coconut/compiler/compiler.py +++ b/coconut/compiler/compiler.py @@ -1061,7 +1061,7 @@ def data_handle(self, original, loc, tokens): extra_stmts += r'''def __new__(_cls, {all_args}): {oind}return _coconut.tuple.__new__(_cls, {base_args_tuple} + {starred_arg}) {cind}@_coconut.classmethod - def _make(cls, iterable, new=_coconut.tuple.__new__, len=_coconut.len): + def _make(cls, iterable, {kwd_only}new=_coconut.tuple.__new__, len=_coconut.len): {oind}result = new(cls, iterable) if len(result) < {num_base_args}: {oind}raise _coconut.TypeError("Expected at least 2 arguments, got %d" % len(result)) @@ -1088,12 +1088,13 @@ def {starred_arg}(self): num_base_args=str(len(base_args)), base_args_tuple="(" + ", ".join(base_args) + ("," if len(base_args) == 1 else "") + ")", quoted_base_args_tuple='("' + '", "'.join(base_args) + '"' + ("," if len(base_args) == 1 else "") + ")", + kwd_only=("*, " if self.target.startswith("3") else ""), ) else: extra_stmts += r'''def __new__(_cls, *{arg}): {oind}return _coconut.tuple.__new__(_cls, {arg}) {cind}@_coconut.classmethod - def _make(cls, iterable, new=_coconut.tuple.__new__, len=None): + def _make(cls, iterable, {kwd_only}new=_coconut.tuple.__new__, len=None): {oind}return new(cls, iterable) {cind}def _asdict(self): {oind}return _coconut.OrderedDict([("{arg}", self[:])]) @@ -1112,6 +1113,7 @@ def {arg}(self): cind=closeindent, name=name, arg=starred_arg, + kwd_only=("*, " if self.target.startswith("3") else ""), ) out = ( "class " + name + "(" From 0bd4142cf41acaf2738260b78cc8a315a6f1389e Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 28 Mar 2017 12:20:30 -0700 Subject: [PATCH 125/176] Make datamaker behave as the data constructor --- DOCS.md | 2 +- HELP.md | 10 +++++----- coconut/compiler/header.py | 20 ++++++++++++++++++-- tests/src/cocotest/agnostic/tutorial.coco | 8 ++++---- 4 files changed, 28 insertions(+), 12 deletions(-) diff --git a/DOCS.md b/DOCS.md index 7ac169065..7a0051355 100644 --- a/DOCS.md +++ b/DOCS.md @@ -1580,7 +1580,7 @@ _Can't be done quickly without Coconut's iterator slicing, which requires many c Coconut provides the `datamaker` function to allow direct access to the base constructor of data types created with the Coconut `data` statement. This is particularly useful when writing alternative constructors for data types by overwriting `__new__`. -For `data` objects, `datamaker` always returns a constructor that takes a single iterator, the values of which are then put into the fields of the data type. +For `data` objects, `datamaker` always returns a constructor that always behaves as the underlying data type's original constructor, exactly as the data type was declared. For non-`data` objects, equivalent to: ```coconut diff --git a/HELP.md b/HELP.md index 880da72c7..f43898820 100644 --- a/HELP.md +++ b/HELP.md @@ -530,7 +530,7 @@ data vector(*pts): match (v is vector,) in pts: return v # vector(v) where v is a vector should return v else: - return pts |> datamaker(cls) # accesses base constructor + return pts |*> datamaker(cls) # accesses base constructor # Test cases: vector(1, 2, 3) |> print # vector(*pts=(1, 2, 3)) @@ -539,7 +539,7 @@ vector(4, 5) |> vector |> print # vector(*pts=(4, 5)) Copy, paste! The big new thing here is how to write `data` constructors. Since `data` types are immutable, `__init__` construction won't work. Instead, a different special method `__new__` is used, which must return the newly constructed instance, and unlike most methods, takes the class not the object as the first argument. Since `__new__` needs to return a fully constructed instance, in almost all cases it will be necessary to access the underlying `data` constructor. To achieve this, Coconut provides the built-in function `datamaker`, which takes a data type, often the first argument to `__new__`, and returns its underlying `data` constructor. -In this case, the constructor checks whether nothing but another `vector` was passed, in which case it returns that, otherwise it returns the result of creating a tuple of the arguments and passing that to the underlying constructor, which takes an iterable and puts its values into the data type's fields. +In this case, the constructor checks whether nothing but another `vector` was passed, in which case it returns that, otherwise it returns the result of creating a tuple of the arguments and passing that to the underlying constructor, the form of which is `vector(*pts)`, since that is how we declared the data type. The other new construct used here is the `|*>`, or star-pipe, operator, which functions exactly like the normal pipe, except that instead of calling the function with one argument, it calls it with as many arguments as there are elements in the sequence passed into it. The difference between `|*>` and `|>` is exactly analogous to the difference between `f(args)` and `f(*args)`. @@ -618,7 +618,7 @@ data vector(*pts): match (v is vector,) in pts: return v # vector(v) where v is a vector should return v else: - return pts |> datamaker(cls) # accesses base constructor + return pts |*> datamaker(cls) # accesses base constructor def __abs__(self) = """Return the magnitude of the vector.""" self.pts |> map$(pow$(?, 2)) |> sum |> pow$(?, 0.5) @@ -832,7 +832,7 @@ data vector(*pts): match (v is vector,) in pts: return v # vector(v) where v is a vector should return v else: - return pts |> datamaker(cls) # accesses base constructor + return pts |*> datamaker(cls) # accesses base constructor def __abs__(self) = """Return the magnitude of the vector.""" self.pts |> map$(pow$(?, 2)) |> sum |> pow$(?, 0.5) @@ -1021,7 +1021,7 @@ data vector(*pts): match (v is vector,) in pts: return v # vector(v) where v is a vector should return v else: - return pts |> datamaker(cls) # accesses base constructor + return pts |*> datamaker(cls) # accesses base constructor def __abs__(self) = """Return the magnitude of the vector.""" self.pts |> map$(pow$(?, 2)) |> sum |> pow$(?, 0.5) diff --git a/coconut/compiler/header.py b/coconut/compiler/header.py index e3227dab3..1a4080710 100644 --- a/coconut/compiler/header.py +++ b/coconut/compiler/header.py @@ -529,11 +529,27 @@ def __repr__(self): args.append("?") for arg in self._stargs: args.append(_coconut.repr(arg)) - return _coconut.repr(self.func) + "$(" + ", ".join(args) + ")" + return _coconut.repr(self.func) + "$(" + ", ".join(args) + ")"''' + if target.startswith("3"): + header += r''' +class _coconut_datamaker:''' + else: + header += r''' +class _coconut_datamaker(object):''' + header += r''' + __slots__ = ("data_type",) + def __init__(self, data_type): + self.data_type = data_type + def __call__(self, *args, **kwargs): + return self.data_type._make(args, **kwargs) + def __repr__(self): + return "datamaker(" + _coconut.repr(data_type) + ")" + def __reduce__(self): + return (_coconut_datamaker, (self.data_type,)) def datamaker(data_type): """Returns base data constructor of passed data type.""" if _coconut.hasattr(data_type, "_make") and (_coconut.issubclass(data_type, _coconut.tuple) or _coconut.isinstance(data_type, _coconut.tuple)): - return data_type._make + return _coconut_datamaker(data_type) else: return _coconut.functools.partial(_coconut.super(data_type, data_type).__new__, data_type) def consume(iterable, keep_last=0): diff --git a/tests/src/cocotest/agnostic/tutorial.coco b/tests/src/cocotest/agnostic/tutorial.coco index 868c2dafb..954ea6914 100644 --- a/tests/src/cocotest/agnostic/tutorial.coco +++ b/tests/src/cocotest/agnostic/tutorial.coco @@ -236,7 +236,7 @@ data vector(*pts): match (v is vector,) in pts: return v # vector(v) where v is a vector should return v else: - return pts |> datamaker(cls) # accesses base constructor + return pts |*> datamaker(cls) # accesses base constructor # Test cases: assert vector(1, 2, 3) |> str == "vector(*pts=(1, 2, 3))" @@ -249,7 +249,7 @@ data vector(*pts): match (v is vector,) in pts: return v # vector(v) where v is a vector should return v else: - return pts |> datamaker(cls) # accesses base constructor + return pts |*> datamaker(cls) # accesses base constructor def __abs__(self) = """Return the magnitude of the vector.""" self.pts |> map$(pow$(?, 2)) |> sum |> pow$(?, 0.5) @@ -324,7 +324,7 @@ data vector(*pts): match (v is vector,) in pts: return v # vector(v) where v is a vector should return v else: - return pts |> datamaker(cls) # accesses base constructor + return pts |*> datamaker(cls) # accesses base constructor def __abs__(self) = """Return the magnitude of the vector.""" self.pts |> map$(pow$(?, 2)) |> sum |> pow$(?, 0.5) @@ -381,7 +381,7 @@ data vector(*pts): match (v is vector,) in pts: return v # vector(v) where v is a vector should return v else: - return pts |> datamaker(cls) # accesses base constructor + return pts |*> datamaker(cls) # accesses base constructor def __abs__(self) = """Return the magnitude of the vector.""" self.pts |> map$(pow$(?, 2)) |> sum |> pow$(?, 0.5) From 0c20c1849e45013b6ad2bf95465cf1ac46c8a7d1 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 28 Mar 2017 12:29:29 -0700 Subject: [PATCH 126/176] Make datamaker a class --- coconut/compiler/header.py | 15 +++++++-------- tests/src/cocotest/agnostic/util.coco | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/coconut/compiler/header.py b/coconut/compiler/header.py index 1a4080710..56ce247a2 100644 --- a/coconut/compiler/header.py +++ b/coconut/compiler/header.py @@ -532,12 +532,17 @@ def __repr__(self): return _coconut.repr(self.func) + "$(" + ", ".join(args) + ")"''' if target.startswith("3"): header += r''' -class _coconut_datamaker:''' +class datamaker:''' else: header += r''' -class _coconut_datamaker(object):''' +class datamaker(object):''' header += r''' __slots__ = ("data_type",) + def __new__(cls, data_type): + if _coconut.hasattr(data_type, "_make") and (_coconut.issubclass(data_type, _coconut.tuple) or _coconut.isinstance(data_type, _coconut.tuple)): + return _coconut.object.__new__(cls) + else: + return _coconut.functools.partial(_coconut.super(data_type, data_type).__new__, data_type) def __init__(self, data_type): self.data_type = data_type def __call__(self, *args, **kwargs): @@ -546,12 +551,6 @@ def __repr__(self): return "datamaker(" + _coconut.repr(data_type) + ")" def __reduce__(self): return (_coconut_datamaker, (self.data_type,)) -def datamaker(data_type): - """Returns base data constructor of passed data type.""" - if _coconut.hasattr(data_type, "_make") and (_coconut.issubclass(data_type, _coconut.tuple) or _coconut.isinstance(data_type, _coconut.tuple)): - return _coconut_datamaker(data_type) - else: - return _coconut.functools.partial(_coconut.super(data_type, data_type).__new__, data_type) def consume(iterable, keep_last=0): """Fully exhaust iterable and return the last keep_last elements.""" return _coconut.collections.deque(iterable, maxlen=keep_last) # fastest way to exhaust an iterator diff --git a/tests/src/cocotest/agnostic/util.coco b/tests/src/cocotest/agnostic/util.coco index fa4b47da2..6eee63447 100644 --- a/tests/src/cocotest/agnostic/util.coco +++ b/tests/src/cocotest/agnostic/util.coco @@ -201,7 +201,7 @@ def is_null(item): return False data Elems(elems): def __new__(cls, *elems) = - elems |> datamaker(cls) + elems |*> datamaker(cls) # Factorial: def factorial1(value): From c8afd451e9d737a1905680a19b7843cc8d3a4d91 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 28 Mar 2017 18:10:12 -0700 Subject: [PATCH 127/176] Fix starred data tests --- HELP.md | 34 +++++++++++------------ tests/src/cocotest/agnostic/tutorial.coco | 24 ++++++++-------- tests/src/cocotest/agnostic/util.coco | 2 +- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/HELP.md b/HELP.md index f43898820..9b5dca550 100644 --- a/HELP.md +++ b/HELP.md @@ -557,18 +557,18 @@ Next up is vector addition. The goal here is to add two vectors of equal length ```coconut def __add__(self, other) = """Add two vectors together.""" - vector(other_pts) = other + vector(*other_pts) = other assert len(other_pts) == len(self.pts) map((+), self.pts, other_pts) |*> vector ``` -There are a couple of new constructs here, but the main notable one is the destructuring assignment statement `vector(other_pts) = other` which showcases the syntax for pattern-matching against data types: it mimics exactly the original `data` declaration of that data type. In this case, `vector(other_pts) = other` will only match a vector, raising a `MatchError` otherwise, and if it does match a vector, will assign the vector's `pts` attribute to the variable `other_pts`. +There are a couple of new constructs here, but the main notable one is the destructuring assignment statement `vector(*other_pts) = other` which showcases the syntax for pattern-matching against data types: it mimics exactly the original `data` declaration of that data type. In this case, `vector(*other_pts) = other` will only match a vector, raising a `MatchError` otherwise, and if it does match a vector, will assign the vector's `pts` attribute to the variable `other_pts`. Next is vector subtraction, which is just like vector addition, but with `(-)` instead of `(+)`: ```coconut def __sub__(self, other) = """Subtract one vector from another.""" - vector(other_pts) = other + vector(*other_pts) = other assert len(other_pts) == len(self.pts) map((-), self.pts, other_pts) |*> vector ``` @@ -584,7 +584,7 @@ Our next method will be equality. We're again going to use `data` pattern-matchi ```coconut def __eq__(self, other): """Compare whether two vectors are equal.""" - match vector(=self.pts) in other: + match vector(*=self.pts) in other: return True else: return False @@ -597,7 +597,7 @@ The last method we'll implement is multiplication. This one is a little bit tric ```coconut def __mul__(self, other): """Scalar multiplication and dot product.""" - match vector(other_pts) in other: + match vector(*other_pts) in other: assert len(other_pts) == len(self.pts) return map((*), self.pts, other_pts) |> sum # dot product else: @@ -624,12 +624,12 @@ data vector(*pts): self.pts |> map$(pow$(?, 2)) |> sum |> pow$(?, 0.5) def __add__(self, other) = """Add two vectors together.""" - vector(other_pts) = other + vector(*other_pts) = other assert len(other_pts) == len(self.pts) map((+), self.pts, other_pts) |*> vector def __sub__(self, other) = """Subtract one vector from another.""" - vector(other_pts) = other + vector(*other_pts) = other assert len(other_pts) == len(self.pts) map((-), self.pts, other_pts) |*> vector def __neg__(self) = @@ -637,14 +637,14 @@ data vector(*pts): self.pts |> map$((-)) |*> vector def __eq__(self, other): """Compare whether two vectors are equal.""" - match vector(=self.pts) in other: + match vector(*=self.pts) in other: return True else: return False def __ne__(self, other) = not self == other def __mul__(self, other): """Scalar multiplication and dot product.""" - match vector(other_pts) in other: + match vector(*other_pts) in other: assert len(other_pts) == len(self.pts) return map((*), self.pts, other_pts) |> sum # dot product else: @@ -838,12 +838,12 @@ data vector(*pts): self.pts |> map$(pow$(?, 2)) |> sum |> pow$(?, 0.5) def __add__(self, other) = """Add two vectors together.""" - vector(other_pts) = other + vector(*other_pts) = other assert len(other_pts) == len(self.pts) map((+), self.pts, other_pts) |*> vector def __sub__(self, other) = """Subtract one vector from another.""" - vector(other_pts) = other + vector(*other_pts) = other assert len(other_pts) == len(self.pts) map((-), self.pts, other_pts) |*> vector def __neg__(self) = @@ -851,14 +851,14 @@ data vector(*pts): self.pts |> map$((-)) |*> vector def __eq__(self, other): """Compare whether two vectors are equal.""" - match vector(=self.pts) in other: + match vector(*=self.pts) in other: return True else: return False def __ne__(self, other) = not self == other def __mul__(self, other): """Scalar multiplication and dot product.""" - match vector(other_pts) in other: + match vector(*other_pts) in other: assert len(other_pts) == len(self.pts) return map((*), self.pts, other_pts) |> sum # dot product else: @@ -1027,12 +1027,12 @@ data vector(*pts): self.pts |> map$(pow$(?, 2)) |> sum |> pow$(?, 0.5) def __add__(self, other) = """Add two vectors together.""" - vector(other_pts) = other + vector(*other_pts) = other assert len(other_pts) == len(self.pts) map((+), self.pts, other_pts) |*> vector def __sub__(self, other) = """Subtract one vector from another.""" - vector(other_pts) = other + vector(*other_pts) = other assert len(other_pts) == len(self.pts) map((-), self.pts, other_pts) |*> vector def __neg__(self) = @@ -1040,14 +1040,14 @@ data vector(*pts): self.pts |> map$((-)) |*> vector def __eq__(self, other): """Compare whether two vectors are equal.""" - match vector(=self.pts) in other: + match vector(*=self.pts) in other: return True else: return False def __ne__(self, other) = not self == other def __mul__(self, other): """Scalar multiplication and dot product.""" - match vector(other_pts) in other: + match vector(*other_pts) in other: assert len(other_pts) == len(self.pts) return map((*), self.pts, other_pts) |> sum # dot product else: diff --git a/tests/src/cocotest/agnostic/tutorial.coco b/tests/src/cocotest/agnostic/tutorial.coco index 954ea6914..81d7f6248 100644 --- a/tests/src/cocotest/agnostic/tutorial.coco +++ b/tests/src/cocotest/agnostic/tutorial.coco @@ -255,12 +255,12 @@ data vector(*pts): self.pts |> map$(pow$(?, 2)) |> sum |> pow$(?, 0.5) def __add__(self, other) = """Add two vectors together.""" - vector(other_pts) = other + vector(*other_pts) = other assert len(other_pts) == len(self.pts) map((+), self.pts, other_pts) |*> vector def __sub__(self, other) = """Subtract one vector from another.""" - vector(other_pts) = other + vector(*other_pts) = other assert len(other_pts) == len(self.pts) map((-), self.pts, other_pts) |*> vector def __neg__(self) = @@ -268,14 +268,14 @@ data vector(*pts): self.pts |> map$((-)) |*> vector def __eq__(self, other): """Compare whether two vectors are equal.""" - match vector(=self.pts) in other: + match vector(*=self.pts) in other: return True else: return False def __ne__(self, other) = not self == other def __mul__(self, other): """Scalar multiplication and dot product.""" - match vector(other_pts) in other: + match vector(*other_pts) in other: assert len(other_pts) == len(self.pts) return map((*), self.pts, other_pts) |> sum # dot product else: @@ -330,12 +330,12 @@ data vector(*pts): self.pts |> map$(pow$(?, 2)) |> sum |> pow$(?, 0.5) def __add__(self, other) = """Add two vectors together.""" - vector(other_pts) = other + vector(*other_pts) = other assert len(other_pts) == len(self.pts) map((+), self.pts, other_pts) |*> vector def __sub__(self, other) = """Subtract one vector from another.""" - vector(other_pts) = other + vector(*other_pts) = other assert len(other_pts) == len(self.pts) map((-), self.pts, other_pts) |*> vector def __neg__(self) = @@ -343,14 +343,14 @@ data vector(*pts): self.pts |> map$((-)) |*> vector def __eq__(self, other): """Compare whether two vectors are equal.""" - match vector(=self.pts) in other: + match vector(*=self.pts) in other: return True else: return False def __ne__(self, other) = not self == other def __mul__(self, other): """Scalar multiplication and dot product.""" - match vector(other_pts) in other: + match vector(*other_pts) in other: assert len(other_pts) == len(self.pts) return map((*), self.pts, other_pts) |> sum # dot product else: @@ -387,12 +387,12 @@ data vector(*pts): self.pts |> map$(pow$(?, 2)) |> sum |> pow$(?, 0.5) def __add__(self, other) = """Add two vectors together.""" - vector(other_pts) = other + vector(*other_pts) = other assert len(other_pts) == len(self.pts) map((+), self.pts, other_pts) |*> vector def __sub__(self, other) = """Subtract one vector from another.""" - vector(other_pts) = other + vector(*other_pts) = other assert len(other_pts) == len(self.pts) map((-), self.pts, other_pts) |*> vector def __neg__(self) = @@ -400,14 +400,14 @@ data vector(*pts): self.pts |> map$((-)) |*> vector def __eq__(self, other): """Compare whether two vectors are equal.""" - match vector(=self.pts) in other: + match vector(*=self.pts) in other: return True else: return False def __ne__(self, other) = not self == other def __mul__(self, other): """Scalar multiplication and dot product.""" - match vector(other_pts) in other: + match vector(*other_pts) in other: assert len(other_pts) == len(self.pts) return map((*), self.pts, other_pts) |> sum # dot product else: diff --git a/tests/src/cocotest/agnostic/util.coco b/tests/src/cocotest/agnostic/util.coco index 6eee63447..fa4b47da2 100644 --- a/tests/src/cocotest/agnostic/util.coco +++ b/tests/src/cocotest/agnostic/util.coco @@ -201,7 +201,7 @@ def is_null(item): return False data Elems(elems): def __new__(cls, *elems) = - elems |*> datamaker(cls) + elems |> datamaker(cls) # Factorial: def factorial1(value): From 30bd671f5224040c373b95c0462c5e05c295e8ec Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Wed, 29 Mar 2017 12:53:09 -0700 Subject: [PATCH 128/176] Bump develop version --- coconut/root.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coconut/root.py b/coconut/root.py index 2e22b438c..0a51cdaff 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -26,7 +26,7 @@ VERSION = "1.2.2" VERSION_NAME = "Colonel" # False for release, int >= 1 for develop -DEVELOP = 10 +DEVELOP = 11 #----------------------------------------------------------------------------------------------------------------------- # CONSTANTS: From 67e58dfeda08aab37855e21fe2d26417a2cdb02a Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Thu, 30 Mar 2017 14:45:17 -0700 Subject: [PATCH 129/176] Remove unnecessary builtin overriding in stub file --- coconut/stubs/__coconut__.pyi | 4 ---- 1 file changed, 4 deletions(-) diff --git a/coconut/stubs/__coconut__.pyi b/coconut/stubs/__coconut__.pyi index eefb850c7..e072a096a 100644 --- a/coconut/stubs/__coconut__.pyi +++ b/coconut/stubs/__coconut__.pyi @@ -24,7 +24,6 @@ if sys.version_info < (3,): from io import open py_raw_input, py_xrange = _b.raw_input, _b.xrange - # chr, str = _b.unichr, _b.unicode class range: def __init__(self, @@ -40,9 +39,6 @@ if sys.version_info < (3,): def __hash__(self) -> int: ... def count(self, elem: int) -> int: ... def index(self, elem: int) -> int: ... -else: - import builtins as _b - ascii, filter, hex, map, oct, zip, open, chr, str, range = _b.ascii, _b.filter, _b.hex, _b.map, _b.oct, _b.zip, _b.open, _b.chr, _b.str, _b.range py_chr, py_filter, py_hex, py_input, py_int, py_map, py_object, py_oct, py_open, py_print, py_range, py_str, py_zip, py_filter, py_reversed, py_enumerate = _b.chr, _b.filter, _b.hex, _b.input, _b.int, _b.map, _b.object, _b.oct, _b.open, _b.print, _b.range, _b.str, _b.zip, _b.filter, _b.reversed, _b.enumerate From 15472f854559fe3dade29990083de9f54d98730a Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Fri, 31 Mar 2017 19:36:48 -0700 Subject: [PATCH 130/176] Add --no-tco option Ref #232. --- DOCS.md | 59 +++++++++++++++++++++--------------- coconut/command/cli.py | 9 ++++-- coconut/command/command.py | 8 +++-- coconut/compiler/compiler.py | 10 ++++-- tests/main_test.py | 17 ++++++----- 5 files changed, 63 insertions(+), 40 deletions(-) diff --git a/DOCS.md b/DOCS.md index 7a0051355..9b4b86b35 100644 --- a/DOCS.md +++ b/DOCS.md @@ -118,9 +118,10 @@ which will install the most recent working [development build](https://github.co ``` coconut [-h] [-v] [-t version] [-i] [-p] [-a] [-l] [-k] [-w] [-r] [-n] - [-d] [-q] [-s] [-c code] [-j processes] [-f] [--minify] - [--jupyter ...] [--mypy ...] [--tutorial] [--documentation] - [--style name] [--recursion-limit limit] [--verbose] + [-d] [-q] [-s] [--no-tco] [-c code] [-j processes] [-f] + [--minify] [--jupyter ...] [--mypy ...] [--tutorial] + [--documentation] [--style name] [--recursion-limit limit] + [--verbose] [--trace] [source] [dest] ``` @@ -135,52 +136,55 @@ dest destination directory for compiled files (defaults to #### Optional Arguments ``` --h, --help show this help message and exit --v, --version print Coconut and Python version information +-h, --help show this help message and exit +-v, --version print Coconut and Python version information -t version, --target version specify target Python version (defaults to universal) --i, --interact force the interpreter to start (otherwise starts if no +-i, --interact force the interpreter to start (otherwise starts if no other command is given) (implies --run) --p, --package compile source as part of a package (defaults to only +-p, --package compile source as part of a package (defaults to only if source is a directory) --a, --standalone compile source as standalone files (defaults to only +-a, --standalone compile source as standalone files (defaults to only if source is a single file) -l, --line-numbers, --linenumbers add line number comments for ease of debugging -k, --keep-lines, --keeplines include source code in comments for ease of debugging --w, --watch watch a directory and recompile on changes --r, --run execute compiled Python --n, --nowrite disable writing compiled Python --d, --display print compiled Python --q, --quiet suppress all informational output (combine with +-w, --watch watch a directory and recompile on changes +-r, --run execute compiled Python +-n, --no-write, --nowrite + disable writing compiled Python +-d, --display print compiled Python +-q, --quiet suppress all informational output (combine with --display to write runnable code to stdout) --s, --strict enforce code cleanliness standards --c code, --code code - run Coconut passed in as a string (can also be piped +-s, --strict enforce code cleanliness standards +--no-tco, --notco disable tail call optimization for ease of debugging +-c code, --code code run Coconut passed in as a string (can also be piped into stdin) -j processes, --jobs processes number of additional processes to use (defaults to 0) (pass 'sys' to use machine default) --f, --force force overwriting of compiled Python (otherwise only +-f, --force force overwriting of compiled Python (otherwise only overwrites when source code or compilation parameters change) ---minify reduce size of compiled Python +--minify reduce size of compiled Python --jupyter ..., --ipython ... run Jupyter/IPython with Coconut as the kernel (remaining args passed to Jupyter) ---mypy ... run MyPy on compiled Python (remaining args passed to - MyPy) (implies --package) ---tutorial open the Coconut tutorial in the default web browser ---documentation open the Coconut documentation in the default web +--mypy ... run MyPy on compiled Python (remaining args passed to + MyPy) (implies --package --no-tco) +--tutorial open the Coconut tutorial in the default web browser +--documentation open the Coconut documentation in the default web browser ---style name Pygments syntax highlighting style (or 'none' to +--style name Pygments syntax highlighting style (or 'none' to disable) (defaults to COCONUT_STYLE environment variable, if it exists, otherwise 'default') --recursion-limit limit, --recursionlimit limit set maximum recursion depth in compiler (defaults to 2000) ---verbose print verbose debug output +--verbose print verbose debug output +--trace show verbose parsing data (only available in coconut- + develop) ``` ### Coconut Scripts @@ -277,6 +281,8 @@ Coconut even supports `--mypy` in the interpreter, which will intelligently scan :14: error: Incompatible types in assignment (expression has type "int", variable has type "str") ``` +_Note: Since [tail call optimization](#tail-call-optimization) prevents proper type-checking, `--mypy` implicitly disables it._ + ## Operators ### Lambdas @@ -978,6 +984,8 @@ _Note: Tail call optimization (though not tail recursion elimination) will work If you are encountering a `RuntimeError` due to maximum recursion depth, it is highly recommended that you rewrite your function to meet either the criteria above for tail call optimization, or the corresponding criteria for [`recursive_iterator`](#recursive-iterator), either of which should prevent such errors. +_Note: Tail call optimization and tail recursion elimination can be turned off by passing the `--no-tco` command-line option._ + ##### Example ###### Coconut @@ -1823,7 +1831,7 @@ Each _mode_ has two components: what parser it uses, and what header it prepends #### `setup` -**coconut.convenience.setup**(_target, strict, minify, line\_numbers, keep\_lines_**)** +**coconut.convenience.setup**(_target, strict, minify, line\_numbers, keep\_lines, no\_tco_**)** `setup` can be used to pass command line flags for use in `parse`. The possible values for each flag argument are: @@ -1832,6 +1840,7 @@ Each _mode_ has two components: what parser it uses, and what header it prepends - _minify_: `False` (default) or `True` - _line\_numbers_: `False` (default) or `True` - _keep\_lines_: `False` (default) or `True` +- _no\_tco_: `False` (default) or `True` #### `cmd` diff --git a/coconut/command/cli.py b/coconut/command/cli.py index b69b22487..d05dac0cc 100644 --- a/coconut/command/cli.py +++ b/coconut/command/cli.py @@ -100,7 +100,7 @@ help="execute compiled Python") arguments.add_argument( - "-n", "--nowrite", + "-n", "--no-write", "--nowrite", action="store_true", help="disable writing compiled Python") @@ -119,6 +119,11 @@ action="store_true", help="enforce code cleanliness standards") +arguments.add_argument( + "--no-tco", "--notco", + action="store_true", + help="disable tail call optimization for ease of debugging") + arguments.add_argument( "-c", "--code", metavar="code", @@ -151,7 +156,7 @@ "--mypy", type=str, nargs=argparse.REMAINDER, - help="run MyPy on compiled Python (remaining args passed to MyPy) (implies --package)") + help="run MyPy on compiled Python (remaining args passed to MyPy) (implies --package --no-tco)") arguments.add_argument( "--tutorial", diff --git a/coconut/command/command.py b/coconut/command/command.py index 212ea013b..7644f46df 100644 --- a/coconut/command/command.py +++ b/coconut/command/command.py @@ -153,17 +153,18 @@ def use_args(self, args, interact=True): if args.tutorial: launch_tutorial() + if args.mypy is not None: + self.set_mypy_args(args.mypy) + self.setup( target=args.target, strict=args.strict, minify=args.minify, line_numbers=args.line_numbers, keep_lines=args.keep_lines, + no_tco=args.no_tco or self.mypy, ) - if args.mypy is not None: - self.set_mypy_args(args.mypy) - if args.source is not None: if args.interact and args.run: logger.warn("extraneous --run argument passed; --interact implies --run") @@ -192,6 +193,7 @@ def use_args(self, args, interact=True): raise CoconutException("destination path must point to directory not file") else: dest = args.dest + if args.package or self.mypy: package = True elif args.standalone: diff --git a/coconut/compiler/compiler.py b/coconut/compiler/compiler.py index 0d257adb7..4670e3ccb 100644 --- a/coconut/compiler/compiler.py +++ b/coconut/compiler/compiler.py @@ -246,7 +246,7 @@ def __init__(self, *args, **kwargs): """Creates a new compiler with the given parsing parameters.""" self.setup(*args, **kwargs) - def setup(self, target=None, strict=False, minify=False, line_numbers=False, keep_lines=False): + def setup(self, target=None, strict=False, minify=False, line_numbers=False, keep_lines=False, no_tco=False): """Initializes parsing parameters.""" if target is None: target = "" @@ -257,11 +257,12 @@ def setup(self, target=None, strict=False, minify=False, line_numbers=False, kee if target not in targets: raise CoconutException('unsupported target Python version "' + target + '" (supported targets are "' + '", "'.join(specific_targets) + '", or leave blank for universal)') - self.target, self.strict, self.minify, self.line_numbers, self.keep_lines = target, strict, minify, line_numbers, keep_lines + self.target, self.strict, self.minify, self.line_numbers, self.keep_lines, self.no_tco = ( + target, strict, minify, line_numbers, keep_lines, no_tco) def __reduce__(self): """Return pickling information.""" - return (Compiler, (self.target, self.strict, self.minify, self.line_numbers, self.keep_lines)) + return (Compiler, (self.target, self.strict, self.minify, self.line_numbers, self.keep_lines, self.no_tco)) def genhash(self, package, code): """Generates a hash from code.""" @@ -1396,6 +1397,9 @@ def decoratable_normal_funcdef_stmt_handle(self, tokens): else: raise CoconutInternalException("invalid function definition tokens", tokens) + if self.no_tco: + return (decorators if decorators is not None else "") + funcdef + lines = [] # transformed tco = False # whether tco was done tre = False # wether tre was done diff --git a/tests/main_test.py b/tests/main_test.py index 8db9f6c7f..e4de29575 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -300,6 +300,12 @@ def test_normal(self): def test_target(self): run(agnostic_target=(2 if PY2 else 3)) + def test_line_numbers(self): + run(["--linenumbers"]) + + def test_keep_lines(self): + run(["--keeplines"]) + if platform.python_implementation() != "PyPy": def test_jobs_zero(self): run(["--jobs", "0"]) @@ -309,9 +315,6 @@ def test_mypy(self): call(["coconut", "-c", mypy_snip, "--mypy"], assert_output=mypy_snip_err, check_mypy=False) run(["--mypy", "--ignore-missing-imports"]) - def test_strict(self): - run(["--strict"]) - def test_run(self): run(use_run_arg=True) @@ -321,11 +324,11 @@ def test_package(self): def test_standalone(self): run(["--standalone"]) - def test_line_numbers(self): - run(["--linenumbers"]) + def test_strict(self): + run(["--strict"]) - def test_keep_lines(self): - run(["--keeplines"]) + def test_no_tco(self): + run(["--no-tco"]) def test_minify(self): run(["--minify"]) From bbb10fa1fdb26c06dc821fa1fb5d020500e11b47 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Fri, 31 Mar 2017 19:42:43 -0700 Subject: [PATCH 131/176] Fix nowrite error --- coconut/command/command.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/coconut/command/command.py b/coconut/command/command.py index 7644f46df..82f80b107 100644 --- a/coconut/command/command.py +++ b/coconut/command/command.py @@ -183,11 +183,11 @@ def use_args(self, args, interact=True): raise CoconutException("source path must point to directory not file when --watch is enabled") if args.dest is None: - if args.nowrite: + if args.no_write: dest = None # no dest else: dest = True # auto-generate dest - elif args.nowrite: + elif args.no_write: raise CoconutException("destination path cannot be given when --nowrite is enabled") elif os.path.isfile(args.dest): raise CoconutException("destination path must point to directory not file") @@ -206,7 +206,7 @@ def use_args(self, args, interact=True): self.run_mypy(filepaths) elif (args.run - or args.nowrite + or args.no_write or args.force or args.package or args.standalone From 81245c71c3d4511c84adca7010975855a4153f1c Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Fri, 31 Mar 2017 20:53:34 -0700 Subject: [PATCH 132/176] Fix dotted function definition --- coconut/compiler/compiler.py | 70 ++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/coconut/compiler/compiler.py b/coconut/compiler/compiler.py index 4670e3ccb..a7a4bb1a2 100644 --- a/coconut/compiler/compiler.py +++ b/coconut/compiler/compiler.py @@ -1397,9 +1397,6 @@ def decoratable_normal_funcdef_stmt_handle(self, tokens): else: raise CoconutInternalException("invalid function definition tokens", tokens) - if self.no_tco: - return (decorators if decorators is not None else "") + funcdef - lines = [] # transformed tco = False # whether tco was done tre = False # wether tre was done @@ -1423,39 +1420,42 @@ def decoratable_normal_funcdef_stmt_handle(self, tokens): self.tre_store_count += 1 attempt_tre = True - for line in raw_lines: - body, indent = split_trailing_indent(line) - level += ind_change(body) - if disabled_until_level is not None: - if level <= disabled_until_level: - disabled_until_level = None - if disabled_until_level is None: - if match_in(Keyword("yield"), body): - # we can't tco generators - lines = raw_lines - break - elif match_in(Keyword("def") | Keyword("try") | Keyword("with"), body): - disabled_until_level = level - else: - base, comment = split_comment(body) - # tco works with decorators, but not tre - if decorators or not attempt_tre: - tre_base = None + if self.no_tco: + lines = raw_lines + else: + for line in raw_lines: + body, indent = split_trailing_indent(line) + level += ind_change(body) + if disabled_until_level is not None: + if level <= disabled_until_level: + disabled_until_level = None + if disabled_until_level is None: + if match_in(Keyword("yield"), body): + # we can't tco generators + lines = raw_lines + break + elif match_in(Keyword("def") | Keyword("try") | Keyword("with"), body): + disabled_until_level = level else: - # attempt tre - tre_base = transform(self.tre_return(func_name, func_args, func_store, use_mock=use_mock), base) - if tre_base is not None: - line = tre_base + comment + indent - tre = True - tco = True # tre falls back on tco if the function is changed - if tre_base is None: - # attempt tco - tco_base = transform(self.tco_return, base) - if tco_base is not None: - line = tco_base + comment + indent - tco = True - lines.append(line) - level += ind_change(indent) + base, comment = split_comment(body) + # tco works with decorators, but not tre + if decorators or not attempt_tre: + tre_base = None + else: + # attempt tre + tre_base = transform(self.tre_return(func_name, func_args, func_store, use_mock=use_mock), base) + if tre_base is not None: + line = tre_base + comment + indent + tre = True + tco = True # tre falls back on tco if the function is changed + if tre_base is None: + # attempt tco + tco_base = transform(self.tco_return, base) + if tco_base is not None: + line = tco_base + comment + indent + tco = True + lines.append(line) + level += ind_change(indent) out = "".join(lines) if tre: From 01bdcdc4cbb2d5a1efaff74782511a1bb6f3a206 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Fri, 31 Mar 2017 21:23:28 -0700 Subject: [PATCH 133/176] Fix mypy, keyboard interrupt, tco tracebacks --- coconut/command/command.py | 21 ++++++++++++--------- coconut/compiler/header.py | 4 ++-- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/coconut/command/command.py b/coconut/command/command.py index 82f80b107..6de285048 100644 --- a/coconut/command/command.py +++ b/coconut/command/command.py @@ -153,18 +153,18 @@ def use_args(self, args, interact=True): if args.tutorial: launch_tutorial() - if args.mypy is not None: - self.set_mypy_args(args.mypy) - self.setup( target=args.target, strict=args.strict, minify=args.minify, line_numbers=args.line_numbers, keep_lines=args.keep_lines, - no_tco=args.no_tco or self.mypy, + no_tco=args.no_tco or args.mypy is not None, ) + if args.mypy is not None: + self.set_mypy_args(args.mypy) + if args.source is not None: if args.interact and args.run: logger.warn("extraneous --run argument passed; --interact implies --run") @@ -452,11 +452,14 @@ def start_prompt(self): print('(type "exit()" or press Ctrl-D to end)') self.start_running() while self.running: - code = self.get_input() - if code: - compiled = self.handle_input(code) - if compiled: - self.execute(compiled, use_eval=None) + try: + code = self.get_input() + if code: + compiled = self.handle_input(code) + if compiled: + self.execute(compiled, use_eval=None) + except KeyboardInterrupt: + printerr("\nKeyboardInterrupt") def exit_runner(self, exit_code=0): """Exits the interpreter.""" diff --git a/coconut/compiler/header.py b/coconut/compiler/header.py index 56ce247a2..9ad9e1dca 100644 --- a/coconut/compiler/header.py +++ b/coconut/compiler/header.py @@ -175,11 +175,11 @@ def tail_call_optimized_func(*args, **kwargs): while True: if "_coconut_inside_tco" in kwargs: del kwargs["_coconut_inside_tco"] - return call_func(*args, **kwargs) + return call_func(*args, **kwargs) # pass --no-tco to clean up your traceback if hasattr(call_func, "_coconut_is_tco"): kwargs["_coconut_inside_tco"] = call_func._coconut_is_tco try: - return call_func(*args, **kwargs) + return call_func(*args, **kwargs) # pass --no-tco to clean up your traceback except _coconut_tail_call as tail_call: call_func, args, kwargs = tail_call.func, tail_call.args, tail_call.kwargs tail_call_optimized_func._coconut_is_tco = True From f6ebd09486b1844c2bf3a74f9267875769346f62 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Fri, 31 Mar 2017 21:33:22 -0700 Subject: [PATCH 134/176] Fix tco tests, improve header performance --- coconut/compiler/header.py | 15 +++++++++------ tests/src/cocotest/agnostic/main.coco | 6 +++++- tests/src/cocotest/agnostic/suite.coco | 9 ++++++--- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/coconut/compiler/header.py b/coconut/compiler/header.py index 9ad9e1dca..3fe577a3f 100644 --- a/coconut/compiler/header.py +++ b/coconut/compiler/header.py @@ -156,10 +156,10 @@ class _coconut(object):''' import collections.abc as abc''' if target.startswith("3"): header += r''' - IndexError, NameError, TypeError, ValueError, classmethod, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, issubclass, iter, len, list, map, min, max, next, object, property, range, reversed, set, slice, str, sum, super, tuple, zip, repr = IndexError, NameError, TypeError, ValueError, classmethod, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, issubclass, iter, len, list, map, min, max, next, object, property, range, reversed, set, slice, str, sum, super, tuple, zip, repr''' + IndexError, KeyError, NameError, TypeError, ValueError, classmethod, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, issubclass, iter, len, list, map, min, max, next, object, property, range, reversed, set, slice, str, sum, super, tuple, zip, repr = IndexError, KeyError, NameError, TypeError, ValueError, classmethod, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, issubclass, iter, len, list, map, min, max, next, object, property, range, reversed, set, slice, str, sum, super, tuple, zip, repr''' else: header += r''' - IndexError, NameError, TypeError, ValueError, classmethod, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, issubclass, iter, len, list, map, min, max, next, object, property, range, reversed, set, slice, str, sum, super, tuple, zip, repr, bytearray = IndexError, NameError, TypeError, ValueError, classmethod, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, issubclass, iter, len, list, map, min, max, next, object, property, range, reversed, set, slice, str, sum, super, tuple, zip, staticmethod(repr), bytearray''' + IndexError, KeyError, NameError, TypeError, ValueError, classmethod, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, issubclass, iter, len, list, map, min, max, next, object, property, range, reversed, set, slice, str, sum, super, tuple, zip, repr, bytearray = IndexError, KeyError, NameError, TypeError, ValueError, classmethod, dict, enumerate, filter, frozenset, getattr, hasattr, hash, int, isinstance, issubclass, iter, len, list, map, min, max, next, object, property, range, reversed, set, slice, str, sum, super, tuple, zip, staticmethod(repr), bytearray''' header += r''' class MatchError(Exception): """Pattern-matching error.""" @@ -173,10 +173,13 @@ def _coconut_tco(func): def tail_call_optimized_func(*args, **kwargs): call_func = func while True: - if "_coconut_inside_tco" in kwargs: + try: del kwargs["_coconut_inside_tco"] + except _coconut.KeyError: + pass + else: return call_func(*args, **kwargs) # pass --no-tco to clean up your traceback - if hasattr(call_func, "_coconut_is_tco"): + if _coconut.hasattr(call_func, "_coconut_is_tco"): kwargs["_coconut_inside_tco"] = call_func._coconut_is_tco try: return call_func(*args, **kwargs) # pass --no-tco to clean up your traceback @@ -462,9 +465,9 @@ def recursive_iterator(func): @_coconut.functools.wraps(func) def recursive_iterator_func(*args, **kwargs): hashable_args_kwargs = _coconut.pickle.dumps((args, kwargs), _coconut.pickle.HIGHEST_PROTOCOL) - if hashable_args_kwargs in tee_store: + try: to_tee = tee_store[hashable_args_kwargs] - else: + except _coconut.KeyError: to_tee = func(*args, **kwargs) tee_store[hashable_args_kwargs], to_return = _coconut_tee(to_tee) return to_return diff --git a/tests/src/cocotest/agnostic/main.coco b/tests/src/cocotest/agnostic/main.coco index ea57c11a7..f5d57038b 100644 --- a/tests/src/cocotest/agnostic/main.coco +++ b/tests/src/cocotest/agnostic/main.coco @@ -340,6 +340,8 @@ def main_test(): assert not isinstance(os, pyobjsub) return True +def test_tco(x) = test_tco(x) + def main(*args): """Asserts arguments and executes tests.""" assert all(args) @@ -350,8 +352,10 @@ def main(*args): if not (3,) <= sys.version_info < (3, 3): from .specific import non_py32_test assert non_py32_test - from .suite import suite_test + from .suite import suite_test, tco_test assert suite_test() + if hasattr(test_tco, "_coconut_is_tco"): + assert tco_test() if sys.version_info < (3,): from .py2_test import py2_test assert py2_test() diff --git a/tests/src/cocotest/agnostic/suite.coco b/tests/src/cocotest/agnostic/suite.coco index 531a1c673..371d09598 100644 --- a/tests/src/cocotest/agnostic/suite.coco +++ b/tests/src/cocotest/agnostic/suite.coco @@ -222,9 +222,6 @@ def suite_test(): assert vector(1, 2) |> .__eq__(other=vector(1, 2)) assert fib() |> takewhile$((i) -> i < 4000000 ) |> filter$((i) -> i % 2 == 0 ) |> sum == 4613732 assert loop([1,2])$[:4] |> list == [1, 2] * 2 - assert recurse_n_times(10000) - assert is_even(5000) and is_odd(5001) - assert is_even_(5000) and is_odd_(5001) assert (def -> mod)()(5, 3) == 2 assert sieve((2, 3, 4, 5)) |> list == [2, 3, 5] assert 11 == double_plus_one(5) @@ -340,3 +337,9 @@ def suite_test(): assert Pred(0, 1, 2) |> fmap$(-> _+1) == Pred(1, 2, 3) assert Quant(0, 1, 2) |> fmap$(-> _+1) == Quant(1, 2, 3) return True + +def tco_test(): + """Exectues suite tests that rely on TCO.""" + assert recurse_n_times(10000) + assert is_even(5000) and is_odd(5001) + assert is_even_(5000) and is_odd_(5001) From e44565f0702831fff0ec3f473dce36c9bc3353e0 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 1 Apr 2017 01:56:56 -0700 Subject: [PATCH 135/176] Fix TCO test --- tests/src/cocotest/agnostic/suite.coco | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/src/cocotest/agnostic/suite.coco b/tests/src/cocotest/agnostic/suite.coco index 371d09598..babc7279c 100644 --- a/tests/src/cocotest/agnostic/suite.coco +++ b/tests/src/cocotest/agnostic/suite.coco @@ -343,3 +343,4 @@ def tco_test(): assert recurse_n_times(10000) assert is_even(5000) and is_odd(5001) assert is_even_(5000) and is_odd_(5001) + return True From 0b2a593f85944ad3c9096d672415cef8e39f789b Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Sat, 1 Apr 2017 12:44:08 -0700 Subject: [PATCH 136/176] Bump develop version --- coconut/root.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coconut/root.py b/coconut/root.py index 0a51cdaff..a7573870d 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -26,7 +26,7 @@ VERSION = "1.2.2" VERSION_NAME = "Colonel" # False for release, int >= 1 for develop -DEVELOP = 11 +DEVELOP = 12 #----------------------------------------------------------------------------------------------------------------------- # CONSTANTS: From d40099c1e03d4ed3ecc2d05d793c47eb36484d35 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Mon, 3 Apr 2017 23:01:13 -0700 Subject: [PATCH 137/176] Add data inheritance --- DOCS.md | 8 ++++---- coconut/compiler/compiler.py | 12 +++++++++--- coconut/compiler/grammar.py | 8 ++++---- tests/src/cocotest/agnostic/suite.coco | 2 +- tests/src/cocotest/agnostic/util.coco | 1 + 5 files changed, 19 insertions(+), 12 deletions(-) diff --git a/DOCS.md b/DOCS.md index 9b4b86b35..58bdc267b 100644 --- a/DOCS.md +++ b/DOCS.md @@ -493,18 +493,18 @@ The syntax for `data` blocks is a cross between the syntax for functions and the Coconut `data` blocks create immutable classes derived from `collections.namedtuple` and made immutable with `__slots__`. Coconut data statement syntax looks like: ```coconut -data (): +data () [from ]: ``` -`` is the name of the new data type, `` are the arguments to its constructor as well as the names of its attributes, and `` contains the data type's methods. +`` is the name of the new data type, `` are the arguments to its constructor as well as the names of its attributes, `` contains the data type's methods, and `` optionally contains any desired base classes. In addition to supporting standard `collections.namedtuple` subclassing when `` is a list of names, Coconut also supports an extended version where `` can contain a starred argument to collect extra parameters. -Subclassing `data` types can be done easily by inheriting from them in a normal Python `class`, although to make the new subclass immutable, the line +Subclassing `data` types can be done easily by inheriting from them either in another `data` statement or a normal Python `class`. If a normal `class` statement is used, making the new subclass immutable will require adding the line ```coconut __slots__ = () ``` -will need to be added to the subclass before any method or attribute definitions. +which will need to be put in the subclass body before any method or attribute definitions. ##### Rationale diff --git a/coconut/compiler/compiler.py b/coconut/compiler/compiler.py index a7a4bb1a2..cdb03e113 100644 --- a/coconut/compiler/compiler.py +++ b/coconut/compiler/compiler.py @@ -1042,6 +1042,9 @@ def data_handle(self, original, loc, tokens): """Processes data blocks.""" if len(tokens) == 3: name, args, stmts = tokens + inherit = None + elif len(tokens) == 4: + name, args, inherit, stmts = tokens else: raise CoconutInternalException("invalid data tokens", tokens) base_args, starred_arg = [], None @@ -1118,9 +1121,12 @@ def {arg}(self): ) out = ( "class " + name + "(" - '_coconut.collections.namedtuple("' + name + '", "' + attr_str + '")' - + (", _coconut.object" if not self.target.startswith("3") else "") - + "):\n" + openindent + '(_coconut.collections.namedtuple("' + name + '", "' + attr_str + '")' + + ( + ", " + inherit if inherit is not None + else ", _coconut.object" if not self.target.startswith("3") + else "" + ) + "):\n" + openindent ) rest = None if "simple" in stmts.keys() and len(stmts) == 1: diff --git a/coconut/compiler/grammar.py b/coconut/compiler/grammar.py index b652a543a..25c6c99d1 100644 --- a/coconut/compiler/grammar.py +++ b/coconut/compiler/grammar.py @@ -715,7 +715,7 @@ class Grammar(object): testlist = trace(itemlist(test, comma)) testlist_star_expr = trace(itemlist(test | star_expr, comma)) - multi_testlist = trace(addspace(OneOrMore(condense(test + comma)) + Optional(test))) + testlist_has_comma = trace(addspace(OneOrMore(condense(test + comma)) + Optional(test))) yield_from = Forward() dict_comp = Forward() @@ -838,7 +838,7 @@ class Grammar(object): set_s = fixto(CaselessLiteral("s"), "s") set_f = fixto(CaselessLiteral("f"), "f") set_letter = set_s | set_f - setmaker = Group(addspace(test + comp_for)("comp") | multi_testlist("list") | test("test")) + setmaker = Group(addspace(test + comp_for)("comp") | testlist_has_comma("list") | test("test")) set_literal_ref = lbrace.suppress() + setmaker + rbrace.suppress() set_letter_literal_ref = set_letter + lbrace.suppress() + Optional(setmaker) + rbrace.suppress() lazy_items = Optional(test + ZeroOrMore(comma.suppress() + test) + Optional(comma.suppress())) @@ -1141,7 +1141,7 @@ class Grammar(object): while_stmt = addspace(Keyword("while") - condense(test - suite - Optional(else_stmt))) for_stmt = addspace(Keyword("for") - assignlist - Keyword("in") - condense(testlist - suite - Optional(else_stmt))) except_clause = attach(Keyword("except").suppress() + ( - multi_testlist("list") | test("test") + testlist_has_comma("list") | test("test") ) - Optional(Keyword("as").suppress() - name), except_handle) try_stmt = condense(Keyword("try") - suite + ( Keyword("finally") - suite @@ -1210,7 +1210,7 @@ class Grammar(object): datadef = Forward() data_args = Group(Optional(lparen.suppress() + Optional( tokenlist(condense(Optional(star) + name), comma) - ) + rparen.suppress())) + ) + rparen.suppress())) + Optional(Keyword("from").suppress() + testlist) data_suite = Group(colon.suppress() - ( (newline.suppress() + indent.suppress() + Optional(docstring) + Group(OneOrMore(stmt)) + dedent.suppress())("complex") | (newline.suppress() + indent.suppress() + docstring + dedent.suppress() | docstring)("docstring") diff --git a/tests/src/cocotest/agnostic/suite.coco b/tests/src/cocotest/agnostic/suite.coco index babc7279c..e5ef79d86 100644 --- a/tests/src/cocotest/agnostic/suite.coco +++ b/tests/src/cocotest/agnostic/suite.coco @@ -62,7 +62,7 @@ def suite_test(): assert 12 |> next_mul_of $(5) == 15 assert collatz(27) assert preop(1, 2).add() == 3 - assert vector(3, 4) |> abs == 5 + assert vector(3, 4) |> abs == 5 == vector_with_id(3, 4, 1) |> abs assert vector(1, 2) |> ((v) -> map(v., ("x", "y"))) |> tuple == (1, 2) assert vector(3, 1) |> vector(1, 2).transform |> ((v) -> map(v[], (0, 1))) |> tuple == (4, 3) assert vector(1, 2) |> vector(1, 2).__eq__ diff --git a/tests/src/cocotest/agnostic/util.coco b/tests/src/cocotest/agnostic/util.coco index fa4b47da2..8fce23e45 100644 --- a/tests/src/cocotest/agnostic/util.coco +++ b/tests/src/cocotest/agnostic/util.coco @@ -202,6 +202,7 @@ def is_null(item): data Elems(elems): def __new__(cls, *elems) = elems |> datamaker(cls) +data vector_with_id(x, y, i) from vector # Factorial: def factorial1(value): From da43a1d816b2ad026d132b1920ae13cf87866bb2 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Mon, 3 Apr 2017 23:10:27 -0700 Subject: [PATCH 138/176] Remove an extra parenthesis --- coconut/compiler/compiler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coconut/compiler/compiler.py b/coconut/compiler/compiler.py index cdb03e113..cf9cbfedc 100644 --- a/coconut/compiler/compiler.py +++ b/coconut/compiler/compiler.py @@ -1121,7 +1121,7 @@ def {arg}(self): ) out = ( "class " + name + "(" - '(_coconut.collections.namedtuple("' + name + '", "' + attr_str + '")' + '_coconut.collections.namedtuple("' + name + '", "' + attr_str + '")' + ( ", " + inherit if inherit is not None else ", _coconut.object" if not self.target.startswith("3") From a842a280bbc46bbc5e1985e745ec249a52104bff Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 18 Apr 2017 16:30:31 -0700 Subject: [PATCH 139/176] Fixes with statement spacing --- coconut/compiler/grammar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coconut/compiler/grammar.py b/coconut/compiler/grammar.py index 25c6c99d1..e1b22481c 100644 --- a/coconut/compiler/grammar.py +++ b/coconut/compiler/grammar.py @@ -1150,7 +1150,7 @@ class Grammar(object): | Keyword("except") - suite ) - Optional(else_stmt) - Optional(Keyword("finally") - suite) )) - with_stmt = addspace(Keyword("with") - with_item_list - suite) + with_stmt = addspace(Keyword("with") - condense(with_item_list - suite)) exec_stmt_ref = Keyword("exec").suppress() + lparen.suppress() + test + Optional( comma.suppress() + test + Optional( comma.suppress() + test + Optional( From 158b2f87583df6ba29533eb05b4e2874f9e8cb6f Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Wed, 26 Apr 2017 14:48:33 -0700 Subject: [PATCH 140/176] Update futures --- coconut/constants.py | 2 +- coconut/requirements.py | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/coconut/constants.py b/coconut/constants.py index a764fcd2c..4fca4d229 100644 --- a/coconut/constants.py +++ b/coconut/constants.py @@ -93,7 +93,7 @@ def fixpath(path): "ipython": (5, 3), "mypy": (0, 501), "prompt_toolkit": (1, 0), - "futures": (3, 0), + "futures": (3, 1), "argparse": (1, 4), "pytest": (3, 0), "watchdog": (0, 8), diff --git a/coconut/requirements.py b/coconut/requirements.py index 25b8932fe..53881a47e 100644 --- a/coconut/requirements.py +++ b/coconut/requirements.py @@ -116,11 +116,23 @@ def latest_version(req): return requests.get(url).json()["info"]["version"] +def ver_tuple(ver_str): + """Convert a version string into a version tuple.""" + out = [] + for x in ver_str.split("."): + try: + x = int(x) + except ValueError: + pass + out.append(x) + return tuple(out) + + def print_new_versions(): """Prints new requirement versions.""" for req in everything_in(all_reqs): new_str = latest_version(req) - new_ver = tuple(int(x) for x in new_str.split(".")) + new_ver = ver_tuple(new_str) updated = False for i, x in enumerate(req_vers[req]): if len(new_ver) <= i or x != new_ver[i]: From e46c7d65f9f4e9bb029983f78edb6a7dec382f26 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Fri, 28 Apr 2017 23:54:58 -0700 Subject: [PATCH 141/176] Pre-release preparations --- .pre-commit-config.yaml | 2 +- coconut/icoconut/root.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 062e9ef1e..2482a20e2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ - repo: git://github.com/pre-commit/pre-commit-hooks.git - sha: a11d9314b22d8f8c7556443875b731ef05965464 + sha: 5bf6c09bfa1297d3692cadd621ef95f1284e33c0 hooks: - id: check-added-large-files - id: check-byte-order-marker diff --git a/coconut/icoconut/root.py b/coconut/icoconut/root.py index defac3fd5..1bd533237 100644 --- a/coconut/icoconut/root.py +++ b/coconut/icoconut/root.py @@ -96,6 +96,7 @@ def cache(self, code, *args, **kwargs): compiled = memoized_parse_sys(code) except CoconutException: traceback.print_exc() + return None else: return super(CoconutCompiler, self).cache(compiled, *args, **kwargs) From 6f8341f4a19c530cf40d4440f1fb0c13aa737eda Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Mon, 1 May 2017 01:18:49 -0700 Subject: [PATCH 142/176] Improve documentation --- DOCS.md | 48 +++++++++++++++++++++++++++++++++++++++++------- FAQ.md | 2 +- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/DOCS.md b/DOCS.md index 58bdc267b..402bd3e52 100644 --- a/DOCS.md +++ b/DOCS.md @@ -548,6 +548,25 @@ def size(Node(l, r)) = size(l) + size(r) size(Node(Empty(), Leaf(10))) == 1 ``` _Showcases the algebraic nature of `data` types when combined with pattern-matching._ +```coconut +data vector(*pts): + """Immutable arbitrary-length vector.""" + + def __abs__(self) = + self.pts |> map$(pow$(?, 2)) |> sum |> pow$(?, 0.5) + + def __add__(self, other) = + vector(*other_pts) = other + assert len(other_pts) == len(self.pts) + map((+), self.pts, other_pts) |*> vector + + def __neg__(self) = + self.pts |> map$((-)) |*> vector + + def __sub__(self, other) = + self + -other +``` +_Showcases starred `data` declaration._ ###### Python ```coconut_python @@ -583,6 +602,7 @@ def size(tree): size(Node(Empty(), Leaf(10))) == 1 ``` +_Starred data declarations can't be done without a long sequence of method definitions. See the compiled code for the Python syntax._ ### `match` @@ -659,7 +679,8 @@ pattern ::= ( - Checks (`=`): will check that whatever is in that position is equal to the previously defined variable ``. - Type Checks (` is `): will check that whatever is in that position is of type(s) `` before binding the ``. - Data Types (`()`): will check that whatever is in that position is of data type `` and will match the attributes to ``. -- Lists (`[]`), Tuples (`()`), or Lazy lists (`(||)`): will only match a sequence (`collections.abc.Sequence`) of the same length, and will check the contents against ``. +- Lists (`[]`), Tuples (`()`): will only match a sequence (`collections.abc.Sequence`) of the same length, and will check the contents against ``. +- Lazy lists (`(||)`): same as list or tuple matching, but checks iterable (`collections.abc.Iterable`) instead of sequence. - Dicts (`{}`): will only match a mapping (`collections.abc.Mapping`) of the same length, and will check the contents against ``. - Sets (`{}`): will only match a set (`collections.abc.Set`) of the same length and contents. - Head-Tail Splits (` + `): will match the beginning of the sequence against the ``, then bind the rest to ``, and make it the type of the construct used. @@ -990,17 +1011,30 @@ _Note: Tail call optimization and tail recursion elimination can be turned off b ###### Coconut ```coconut -# unlike in Python, this function will never hit a maximum recursion depth +# unlike in Python, this function will never hit a maximum recursion depth error def factorial(n, acc=1): - if n == 0: - return acc - else: - return factorial(n-1, acc*n) + case n: + match 0: + return acc + match _ is int if n > 0: + return factorial(n-1, acc*n) +``` +_Showcases tail recursion elimination._ +```coconut +# unlike in Python, neither of these functions will ever hit a maximum recursion depth error +def is_even(0) = True +@addpattern(is_even) +def is_even(n is int if n > 0) = is_odd(n-1) + +def is_odd(0) = False +@addpattern(is_odd) +def is_odd(n is int if n > 0) = is_even(n-1) ``` +_Showcases tail call optimization._ ###### Python -_Can't be done without rewriting the function._ +_Can't be done without rewriting the function(s)._ ### Operator Functions diff --git a/FAQ.md b/FAQ.md index 3b87b6133..39c095889 100644 --- a/FAQ.md +++ b/FAQ.md @@ -75,4 +75,4 @@ If you don't get the reference, the image above is from [Monty Python and the Ho ### Who developed Coconut? -[Evan Hubinger](https://github.com/evhub) is an undergraduate student studying mathematics and computer science at [Harvey Mudd College](https://www.hmc.edu/). You can find him on LinkedIn at . +[Evan Hubinger](https://github.com/evhub) is an undergraduate student studying mathematics and computer science at [Harvey Mudd College](https://www.hmc.edu/). He can be reached by asking a question on [Coconut's Gitter chat room](https://gitter.im/evhub/coconut), through email at , or on [LinkedIn](https://www.linkedin.com/in/ehubinger). From 39f4c7f7ea5efab476768469702021d9238947d4 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Mon, 1 May 2017 12:31:58 -0700 Subject: [PATCH 143/176] Make doc example match website example --- DOCS.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DOCS.md b/DOCS.md index 402bd3e52..318396f69 100644 --- a/DOCS.md +++ b/DOCS.md @@ -533,9 +533,9 @@ v.x = 2 # this will fail because data objects are immutable ``` _Showcases the syntax, features, and immutable nature of `data` types._ ```coconut -data Empty(): pass -data Leaf(n): pass -data Node(l, r): pass +data Empty() +data Leaf(n) +data Node(l, r) def size(Empty()) = 0 From 71c401c9ee905e7ddc0d3f8d881c2073609db7d6 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Mon, 1 May 2017 16:15:47 -0700 Subject: [PATCH 144/176] Start updating code, documentation for pycon --- DOCS.md | 42 ++++++++++++-------------- coconut/icoconut/root.py | 16 ++++++---- coconut/requirements.py | 28 +++++++++++------ tests/src/cocotest/agnostic/suite.coco | 3 +- tests/src/cocotest/agnostic/util.coco | 6 +++- 5 files changed, 56 insertions(+), 39 deletions(-) diff --git a/DOCS.md b/DOCS.md index 318396f69..43590d78d 100644 --- a/DOCS.md +++ b/DOCS.md @@ -426,7 +426,7 @@ def chain(*iterables): ###### Coconut ```coconut -def N(n=0) = (n,) :: N(n+1) # no infinite loop because :: is lazy +def N(n=0) = (n,) :: N(n+1) # no infinite loop because :: is lazy (range(-10, 0) :: N())$[5:15] |> list |> print ``` @@ -522,14 +522,14 @@ Named tuple instances do not have per-instance dictionaries, so they are lightwe ###### Coconut ```coconut -data vector(x, y): +data vector2(x, y): def __abs__(self): return (self.x**2 + self.y**2)**.5 -v = vector(3, 4) -v |> print # all data types come with a built-in __repr__ +v = vector2(3, 4) +v |> print # all data types come with a built-in __repr__ v |> abs |> print -v.x = 2 # this will fail because data objects are immutable +v.x = 2 # this will fail because data objects are immutable ``` _Showcases the syntax, features, and immutable nature of `data` types._ ```coconut @@ -571,12 +571,12 @@ _Showcases starred `data` declaration._ ###### Python ```coconut_python import collections -class vector(collections.namedtuple("vector", "x, y")): +class vector2(collections.namedtuple("vector2", "x, y")): __slots__ = () def __abs__(self): return (self.x**2 + self.y**2)**.5 -v = vector(3, 4) +v = vector2(3, 4) print(v) print(abs(v)) v.x = 2 @@ -699,8 +699,8 @@ When checking whether or not an object can be matched against in a particular fa def factorial(value): match 0 in value: return 1 - else: match n is int in value if n > 0: # possible because of Coconut's - return n * factorial(n-1) # enhanced else statements + else: match n is int in value if n > 0: # possible because of Coconut's + return n * factorial(n-1) # enhanced else statements else: raise TypeError("invalid argument to factorial of: "+repr(value)) @@ -728,7 +728,7 @@ _Showcases matching to data types. Values defined by the user with the `data` st data Empty() data Leaf(n) data Node(l, r) -Tree = (Empty, Leaf, Node) # type union +Tree = (Empty, Leaf, Node) # type union def depth(Tree()) = 0 @@ -744,17 +744,15 @@ Node(Leaf(2), Node(Empty(), Leaf(3))) |> depth |> print ``` _Showcases how the combination of data types and match statements can be used to powerful effect to replicate the usage of algebraic data types in other functional programming languages._ ```coconut -def duplicate_first(value): - match [x] + xs as l in value: - return [x] + l - else: - raise TypeError() +def duplicate_first([x] + xs as l) = + [x] + l [1,2,3] |> duplicate_first |> print ``` _Showcases head-tail splitting, one of the most common uses of pattern-matching, where a `+ ` (or `:: ` for any iterable) at the end of a list or tuple literal can be used to match the rest of the sequence._ ``` -def sieve([head] :: tail) = [head] :: sieve(n for n in tail if n % head) +def sieve([head] :: tail) = + [head] :: sieve(n for n in tail if n % head) @addpattern(sieve) def sieve((||)) = [] @@ -1443,16 +1441,16 @@ Apply _function_ of two arguments cumulatively to the items of _sequence_, from ###### Coconut ```coconut -prod = reduce$(*) -range(1, 10) |> prod |> print +product = reduce$(*) +range(1, 10) |> product |> print ``` ###### Python ```coconut_python import operator import functools -prod = functools.partial(functools.reduce, operator.mul) -print(prod(range(1, 10))) +product = functools.partial(functools.reduce, operator.mul) +print(product(range(1, 10))) ``` ### `takewhile` @@ -1688,8 +1686,8 @@ Coconut provides a `recursive_iterator` decorator that provides significant opti 1. your function either always `return`s an iterator or generates an iterator using `yield`, 2. when called multiple times with the same arguments, your function produces the same iterator (your function is stateless), -3. your function gets called multiple times with the same arguments, and -4. all arguments passed to your function have a unique pickling (this should almost always be true). +3. your function gets called (usually calls itself) multiple times with the same arguments, and +4. all arguments passed to your function have a unique pickling (usually true for most arguments). If you are encountering a `RuntimeError` due to maximum recursion depth, it is highly recommended that you rewrite your function to meet either the criteria above for `recursive_iterator`, or the corresponding criteria for Coconut's [tail call optimization](#tail-call-optimization), either of which should prevent such errors. diff --git a/coconut/icoconut/root.py b/coconut/icoconut/root.py index 1bd533237..68229ebaa 100644 --- a/coconut/icoconut/root.py +++ b/coconut/icoconut/root.py @@ -21,12 +21,6 @@ import traceback -from ipykernel.ipkernel import IPythonKernel -from ipykernel.zmqshell import ZMQInteractiveShell -from IPython.core.inputsplitter import IPythonInputSplitter -from IPython.core.interactiveshell import InteractiveShellABC -from IPython.core.compilerop import CachingCompiler - from coconut.exceptions import CoconutException from coconut.constants import ( py_syntax_version, @@ -40,6 +34,16 @@ from coconut.compiler.util import should_indent from coconut.command.util import Runner +try: + from ipykernel.ipkernel import IPythonKernel + from ipykernel.zmqshell import ZMQInteractiveShell + from IPython.core.inputsplitter import IPythonInputSplitter + from IPython.core.interactiveshell import InteractiveShellABC + from IPython.core.compilerop import CachingCompiler +except ImportError: + raise CoconutException("--ipython flag requires IPython library", + extra="run 'pip install coconut[ipython]' to fix") + #----------------------------------------------------------------------------------------------------------------------- # GLOBALS: #----------------------------------------------------------------------------------------------------------------------- diff --git a/coconut/requirements.py b/coconut/requirements.py index 53881a47e..21b567c5a 100644 --- a/coconut/requirements.py +++ b/coconut/requirements.py @@ -90,19 +90,29 @@ def everything_in(req_dict): + get_reqs("dev") ) + +def add_version_reqs(modern=True): + if modern: + global extras + extras[":python_version<'2.7'"] = get_reqs("py26") + extras[":python_version>='2.7'"] = get_reqs("non-py26") + extras[":python_version<'3'"] = get_reqs("py2") + else: + global requirements + if PY26: + requirements += get_reqs("py26") + else: + requirements += get_reqs("non-py26") + if PY2: + requirements += get_reqs("py2") + + if int(setuptools.__version__.split(".", 1)[0]) < 18: if "bdist_wheel" in sys.argv: raise RuntimeError("bdist_wheel not supported for setuptools versions < 18 (run 'pip install --upgrade setuptools' to fix)") - if PY26: - requirements += get_reqs("py26") - else: - requirements += get_reqs("non-py26") - if PY2: - requirements += get_reqs("py2") + add_version_reqs(modern=False) else: - extras[":python_version<'2.7'"] = get_reqs("py26") - extras[":python_version>='2.7'"] = get_reqs("non-py26") - extras[":python_version<'3'"] = get_reqs("py2") + add_version_reqs() #----------------------------------------------------------------------------------------------------------------------- # MAIN: diff --git a/tests/src/cocotest/agnostic/suite.coco b/tests/src/cocotest/agnostic/suite.coco index e5ef79d86..06932e991 100644 --- a/tests/src/cocotest/agnostic/suite.coco +++ b/tests/src/cocotest/agnostic/suite.coco @@ -16,7 +16,7 @@ def suite_test(): assert all(same((1, 2, 3), [1, 2, 3])) assert chain2((|1, 2|), (|3, 4|)) |> list == [1, 2, 3, 4] assert threeple$(1, 2)(3) == (1, 2, 3) - assert 1 `range` 5 |> prod == 24 + assert 1 `range` 5 |> product == 24 assert plus1(4) == 5 == plus1_(4) assert 2 `plus1` == 3 == plus1(2) assert plus1(plus1(5)) == 7 == (plus1..plus1)(5) @@ -78,6 +78,7 @@ def suite_test(): assert factorial4(3) == 6 assert factorial5(3) == 6 assert fact(3) == 6 == fact_(3) + assert factorial(3) == 6 assert factorial1(-1) is None assert factorial2(-1) is None assert factorial4(-1) is None diff --git a/tests/src/cocotest/agnostic/util.coco b/tests/src/cocotest/agnostic/util.coco index 8fce23e45..07af64afb 100644 --- a/tests/src/cocotest/agnostic/util.coco +++ b/tests/src/cocotest/agnostic/util.coco @@ -16,7 +16,7 @@ def a `join_with` (b=""): return b.join(a) # Basic Functions: -prod = reduce$(*) +product = reduce$(*) def zipwith(f, *args) = map((items) -> f(*items), zip(*args)) zipsum = map$(sum)..zip plus1 = plus$(1) @@ -243,6 +243,10 @@ match def fact(n) = fact(n, 1) match def fact(0, acc) = acc @addpattern(fact) match def fact(n, acc) = fact(n-1, acc*n) +def factorial(0, acc=1) = acc +@addpattern(factorial) +def factorial(n is int, acc=1 if n > 0) = + factorial(n-1, acc*n) # Match Functions: def classify(value): From 3b89240906956c95288119f85069007ba4b1a727 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Mon, 1 May 2017 22:14:09 -0700 Subject: [PATCH 145/176] Attempt to fix conda installation issue --- coconut/requirements.py | 3 ++- coconut/root.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/coconut/requirements.py b/coconut/requirements.py index 21b567c5a..8fbdbc06b 100644 --- a/coconut/requirements.py +++ b/coconut/requirements.py @@ -19,6 +19,7 @@ import sys import platform +import os import setuptools @@ -112,7 +113,7 @@ def add_version_reqs(modern=True): raise RuntimeError("bdist_wheel not supported for setuptools versions < 18 (run 'pip install --upgrade setuptools' to fix)") add_version_reqs(modern=False) else: - add_version_reqs() + add_version_reqs(modern="CONDA_PREFIX" not in os.environ) #----------------------------------------------------------------------------------------------------------------------- # MAIN: diff --git a/coconut/root.py b/coconut/root.py index a7573870d..3374b65e3 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -26,7 +26,7 @@ VERSION = "1.2.2" VERSION_NAME = "Colonel" # False for release, int >= 1 for develop -DEVELOP = 12 +DEVELOP = 13 #----------------------------------------------------------------------------------------------------------------------- # CONSTANTS: From 4c2e7b0edc7809fdf0e62a89ba1627dc75bae139 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Mon, 1 May 2017 23:09:01 -0700 Subject: [PATCH 146/176] Perform more conda fixes --- coconut/constants.py | 4 +++- coconut/icoconut/root.py | 18 ++++++++++++++---- coconut/root.py | 2 +- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/coconut/constants.py b/coconut/constants.py index 4fca4d229..4de560647 100644 --- a/coconut/constants.py +++ b/coconut/constants.py @@ -56,8 +56,9 @@ def fixpath(path): ], "jupyter": [ "jupyter", - "jupyter-console", "ipython", + "ipykernel", + "jupyter-console", ], "mypy": [ "mypy", @@ -91,6 +92,7 @@ def fixpath(path): "jupyter": (1, 0), "jupyter-console": (5, 1), "ipython": (5, 3), + "ipykernel": (4, 6), "mypy": (0, 501), "prompt_toolkit": (1, 0), "futures": (3, 1), diff --git a/coconut/icoconut/root.py b/coconut/icoconut/root.py index 68229ebaa..9f88cdc24 100644 --- a/coconut/icoconut/root.py +++ b/coconut/icoconut/root.py @@ -19,6 +19,7 @@ from coconut.root import * # NOQA +import os import traceback from coconut.exceptions import CoconutException @@ -30,19 +31,28 @@ documentation_url, code_exts, ) +from coconut.terminal import logger from coconut.compiler import Compiler from coconut.compiler.util import should_indent from coconut.command.util import Runner try: - from ipykernel.ipkernel import IPythonKernel - from ipykernel.zmqshell import ZMQInteractiveShell from IPython.core.inputsplitter import IPythonInputSplitter from IPython.core.interactiveshell import InteractiveShellABC from IPython.core.compilerop import CachingCompiler + from ipykernel.ipkernel import IPythonKernel + from ipykernel.zmqshell import ZMQInteractiveShell except ImportError: - raise CoconutException("--ipython flag requires IPython library", - extra="run 'pip install coconut[ipython]' to fix") + if os.environ.get("CONDA_BUILD"): + # conda tries to import coconut.icoconut as a test even when IPython isn't available + logger.warn("Detected CONDA_BUILD; skipping coconut.icoconut loading") + + class FakeClass: + pass + IPythonInputSplitter = InteractiveShellABC = CachingCompiler = IPythonKernel = ZMQInteractiveShell = FakeClass + else: + raise CoconutException("--ipython flag requires IPython library", + extra="run 'pip install coconut[ipython]' to fix") #----------------------------------------------------------------------------------------------------------------------- # GLOBALS: diff --git a/coconut/root.py b/coconut/root.py index 3374b65e3..53ffe8139 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -26,7 +26,7 @@ VERSION = "1.2.2" VERSION_NAME = "Colonel" # False for release, int >= 1 for develop -DEVELOP = 13 +DEVELOP = 14 #----------------------------------------------------------------------------------------------------------------------- # CONSTANTS: From 203c777262ef442f08f609319cb1b5bef4806234 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Mon, 1 May 2017 23:11:19 -0700 Subject: [PATCH 147/176] Use LOAD_MODULE instead of FakeClass --- coconut/icoconut/root.py | 205 +++++++++++++++++++-------------------- 1 file changed, 101 insertions(+), 104 deletions(-) diff --git a/coconut/icoconut/root.py b/coconut/icoconut/root.py index 9f88cdc24..137318f18 100644 --- a/coconut/icoconut/root.py +++ b/coconut/icoconut/root.py @@ -43,16 +43,15 @@ from ipykernel.ipkernel import IPythonKernel from ipykernel.zmqshell import ZMQInteractiveShell except ImportError: + LOAD_MODULE = False if os.environ.get("CONDA_BUILD"): # conda tries to import coconut.icoconut as a test even when IPython isn't available logger.warn("Detected CONDA_BUILD; skipping coconut.icoconut loading") - - class FakeClass: - pass - IPythonInputSplitter = InteractiveShellABC = CachingCompiler = IPythonKernel = ZMQInteractiveShell = FakeClass else: raise CoconutException("--ipython flag requires IPython library", extra="run 'pip install coconut[ipython]' to fix") +else: + LOAD_MODULE = True #----------------------------------------------------------------------------------------------------------------------- # GLOBALS: @@ -92,114 +91,112 @@ def memoized_parse_sys(code): #----------------------------------------------------------------------------------------------------------------------- -class CoconutCompiler(CachingCompiler, object): - """IPython compiler for Coconut.""" +if LOAD_MODULE: - def ast_parse(self, source, *args, **kwargs): - """Version of ast_parse that compiles Coconut code first.""" - try: - compiled = memoized_parse_sys(source) - except CoconutException as err: - raise err.syntax_err() - else: - return super(CoconutCompiler, self).ast_parse(compiled, *args, **kwargs) + class CoconutCompiler(CachingCompiler, object): + """IPython compiler for Coconut.""" - def cache(self, code, *args, **kwargs): - """Version of cache that compiles Coconut code first.""" - try: - compiled = memoized_parse_sys(code) - except CoconutException: - traceback.print_exc() - return None - else: - return super(CoconutCompiler, self).cache(compiled, *args, **kwargs) - - -class CoconutSplitter(IPythonInputSplitter, object): - """IPython splitter for Coconut.""" - - def __init__(self, *args, **kwargs): - """Version of __init__ that sets up Coconut code compilation.""" - super(CoconutSplitter, self).__init__(*args, **kwargs) - self._compile = self._coconut_compile - - def _coconut_compile(self, source, *args, **kwargs): - """Version of _compile that compiles Coconut code. - None means that the code should not be run as is. - Any other value means that it can.""" - if source.endswith("\n\n"): - return True - elif should_indent(source): - return None - elif "\n" not in source.rstrip(): # if at start + def ast_parse(self, source, *args, **kwargs): + """Version of ast_parse that compiles Coconut code first.""" try: - memoized_parse_block(source) + compiled = memoized_parse_sys(source) + except CoconutException as err: + raise err.syntax_err() + else: + return super(CoconutCompiler, self).ast_parse(compiled, *args, **kwargs) + + def cache(self, code, *args, **kwargs): + """Version of cache that compiles Coconut code first.""" + try: + compiled = memoized_parse_sys(code) except CoconutException: + traceback.print_exc() return None else: - return True - else: - return True - - -class CoconutShell(ZMQInteractiveShell, object): - """IPython shell for Coconut.""" - input_splitter = CoconutSplitter(line_input_checker=True) - input_transformer_manager = CoconutSplitter(line_input_checker=False) + return super(CoconutCompiler, self).cache(compiled, *args, **kwargs) - def init_instance_attrs(self): - """Version of init_instance_attrs that uses CoconutCompiler.""" - super(CoconutShell, self).init_instance_attrs() - self.compile = CoconutCompiler() + class CoconutSplitter(IPythonInputSplitter, object): + """IPython splitter for Coconut.""" - def init_create_namespaces(self, *args, **kwargs): - """Version of init_create_namespaces that adds Coconut built-ins to globals.""" - super(CoconutShell, self).init_create_namespaces(*args, **kwargs) - RUNNER.update_vars(self.user_global_ns) + def __init__(self, *args, **kwargs): + """Version of __init__ that sets up Coconut code compilation.""" + super(CoconutSplitter, self).__init__(*args, **kwargs) + self._compile = self._coconut_compile - def run_cell(self, raw_cell, store_history=False, silent=False, shell_futures=None): - """Version of run_cell that always uses shell_futures.""" - return super(CoconutShell, self).run_cell(raw_cell, store_history, silent, shell_futures=True) + def _coconut_compile(self, source, *args, **kwargs): + """Version of _compile that compiles Coconut code. + None means that the code should not be run as is. + Any other value means that it can.""" + if source.endswith("\n\n"): + return True + elif should_indent(source): + return None + elif "\n" not in source.rstrip(): # if at start + try: + memoized_parse_block(source) + except CoconutException: + return None + else: + return True + else: + return True - def user_expressions(self, expressions): - """Version of user_expressions that compiles Coconut code first.""" - compiled_expressions = {} - for key, expr in expressions.items(): - try: - compiled_expressions[key] = COMPILER.parse_eval(expr) - except CoconutException: - compiled_expressions[key] = expr - return super(CoconutShell, self).user_expressions(compiled_expressions) - - -InteractiveShellABC.register(CoconutShell) - - -class CoconutKernel(IPythonKernel, object): - """Jupyter kernel for Coconut.""" - shell_class = CoconutShell - implementation = "icoconut" - implementation_version = VERSION - language = "coconut" - language_version = VERSION - banner = version_banner - language_info = { - "name": "coconut", - "mimetype": mimetype, - "file_extension": code_exts[0], - "codemirror_mode": { - "name": "python", - "version": py_syntax_version - }, - "pygments_lexer": "coconut" - } - help_links = [ - { - "text": "Coconut Tutorial", - "url": tutorial_url - }, - { - "text": "Coconut Documentation", - "url": documentation_url + class CoconutShell(ZMQInteractiveShell, object): + """IPython shell for Coconut.""" + input_splitter = CoconutSplitter(line_input_checker=True) + input_transformer_manager = CoconutSplitter(line_input_checker=False) + + def init_instance_attrs(self): + """Version of init_instance_attrs that uses CoconutCompiler.""" + super(CoconutShell, self).init_instance_attrs() + self.compile = CoconutCompiler() + + def init_create_namespaces(self, *args, **kwargs): + """Version of init_create_namespaces that adds Coconut built-ins to globals.""" + super(CoconutShell, self).init_create_namespaces(*args, **kwargs) + RUNNER.update_vars(self.user_global_ns) + + def run_cell(self, raw_cell, store_history=False, silent=False, shell_futures=None): + """Version of run_cell that always uses shell_futures.""" + return super(CoconutShell, self).run_cell(raw_cell, store_history, silent, shell_futures=True) + + def user_expressions(self, expressions): + """Version of user_expressions that compiles Coconut code first.""" + compiled_expressions = {} + for key, expr in expressions.items(): + try: + compiled_expressions[key] = COMPILER.parse_eval(expr) + except CoconutException: + compiled_expressions[key] = expr + return super(CoconutShell, self).user_expressions(compiled_expressions) + + InteractiveShellABC.register(CoconutShell) + + class CoconutKernel(IPythonKernel, object): + """Jupyter kernel for Coconut.""" + shell_class = CoconutShell + implementation = "icoconut" + implementation_version = VERSION + language = "coconut" + language_version = VERSION + banner = version_banner + language_info = { + "name": "coconut", + "mimetype": mimetype, + "file_extension": code_exts[0], + "codemirror_mode": { + "name": "python", + "version": py_syntax_version + }, + "pygments_lexer": "coconut" } - ] + help_links = [ + { + "text": "Coconut Tutorial", + "url": tutorial_url + }, + { + "text": "Coconut Documentation", + "url": documentation_url + } + ] From dab05cd8b3f865289b3fe5bf841d4d3bf3dd4d34 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Mon, 1 May 2017 23:45:27 -0700 Subject: [PATCH 148/176] Bump develop version --- coconut/root.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coconut/root.py b/coconut/root.py index 53ffe8139..59dea0877 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -26,7 +26,7 @@ VERSION = "1.2.2" VERSION_NAME = "Colonel" # False for release, int >= 1 for develop -DEVELOP = 14 +DEVELOP = 15 #----------------------------------------------------------------------------------------------------------------------- # CONSTANTS: From bf22d6a68a5f636ea6964cdb401555dda081c88e Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 2 May 2017 01:09:04 -0700 Subject: [PATCH 149/176] Improve constants --- DOCS.md | 2 + coconut/compiler/compiler.py | 2 +- coconut/compiler/header.py | 2 +- coconut/compiler/util.py | 5 -- coconut/constants.py | 93 ++++++++++++++++++++++-------------- coconut/icoconut/root.py | 5 +- coconut/root.py | 2 - conf.py | 8 +++- 8 files changed, 69 insertions(+), 50 deletions(-) diff --git a/DOCS.md b/DOCS.md index 43590d78d..b39c18644 100644 --- a/DOCS.md +++ b/DOCS.md @@ -212,6 +212,8 @@ _Note: The tested against implementations are [CPython](https://www.python.org/) As part of Coconut's cross-compatibility efforts, Coconut adds in new Python 3 built-ins and overwrites Python 2 built-ins to use the Python 3 versions where possible. Additionally, Coconut also overrides some Python 3 built-ins for optimization purposes. If access to the Python versions is desired, the old built-ins can be retrieved by prefixing them with `py_`. +For standard library compatibility, Coconut automatically maps imports under Python 3 names to imports under Python 2 names. Thus, Coconut will automatically take care of any standard library modules that were renamed from Python 2 to Python 3 if just the Python 3 name is used. For modules or objects that only exist in Python 3, however, Coconut has no way of maintaining compatibility. + Finally, while Coconut will try to compile Python-3-specific syntax to its universal equivalent, the following constructs have no equivalent in Python 2, and require the specification of a target of at least `3` to be used: - destructuring assignment with `*`s (use Coconut pattern-matching instead), - the `nonlocal` keyword, diff --git a/coconut/compiler/compiler.py b/coconut/compiler/compiler.py index cf9cbfedc..d677de7b0 100644 --- a/coconut/compiler/compiler.py +++ b/coconut/compiler/compiler.py @@ -41,6 +41,7 @@ ) from coconut.constants import ( + get_target_info, specific_targets, targets, pseudo_targets, @@ -92,7 +93,6 @@ match_handle, ) from coconut.compiler.util import ( - get_target_info, addskip, count_end, paren_change, diff --git a/coconut/compiler/header.py b/coconut/compiler/header.py index 3fe577a3f..a3b3d0465 100644 --- a/coconut/compiler/header.py +++ b/coconut/compiler/header.py @@ -20,12 +20,12 @@ from coconut.root import * # NOQA from coconut.constants import ( + get_target_info, hash_prefix, tabideal, default_encoding, ) from coconut.exceptions import CoconutInternalException -from coconut.compiler.util import get_target_info #----------------------------------------------------------------------------------------------------------------------- # MAIN: diff --git a/coconut/compiler/util.py b/coconut/compiler/util.py index a0d521dcc..eae4106b7 100644 --- a/coconut/compiler/util.py +++ b/coconut/compiler/util.py @@ -61,11 +61,6 @@ def longest(*args): return matcher -def get_target_info(target): - """Returns target information as a version tuple.""" - return tuple(int(x) for x in target) - - def addskip(skips, skip): """Adds a line skip to the skips.""" if skip < 1: diff --git a/coconut/constants.py b/coconut/constants.py index 4de560647..c4c250173 100644 --- a/coconut/constants.py +++ b/coconut/constants.py @@ -33,6 +33,28 @@ def fixpath(path): return os.path.normpath(os.path.realpath(path)) +def get_target_info(target): + """Returns target information as a version tuple.""" + return tuple(int(x) for x in target) + + +#----------------------------------------------------------------------------------------------------------------------- +# VERSION CONSTANTS: +#----------------------------------------------------------------------------------------------------------------------- + +version_long = "Version " + VERSION_STR + " running on Python " + sys.version.split()[0] + +version_banner = "Coconut " + VERSION_STR + +if DEVELOP: + version_tag = "develop" +else: + version_tag = "v" + VERSION + +version_str_tag = "v" + VERSION_STR + +version_tuple = VERSION.split(".") + #----------------------------------------------------------------------------------------------------------------------- # INSTALLATION CONSTANTS: #----------------------------------------------------------------------------------------------------------------------- @@ -133,45 +155,42 @@ def fixpath(path): ] search_terms = [ - "functional programming language", - "functional programming", "functional", - "programming language", + "programming", + "language", "compiler", "match", - "matches", - "matching", + "pattern", "pattern-matching", - "pattern matching", - "algebraic data type", - "algebraic data types", + "algebraic", "data", - "data type", - "data types", + "type", + "types", "lambda", "lambdas", + "lazy", + "evaluation", "lazy list", "lazy lists", - "lazy evaluation", - "lazy", - "tail recursion", - "tail call", - "optimization", + "tail", "recursion", + "call", "recursive", "infix", - "function composition", - "partial application", + "function", + "composition", + "partial", + "application", "currying", "curry", "pipeline", "pipe", - "unicode operator", - "unicode operators", + "unicode", + "operator", + "operators", "frozenset literal", - "frozenset literals", "destructuring", - "destructuring assignment", + "assignment", "reduce", "takewhile", "dropwhile", @@ -185,18 +204,19 @@ def fixpath(path): "addpattern", "prepattern", "recursive_iterator", + "iterator", "fmap", - "data keyword", - "match keyword", - "case keyword", + "case", + "keyword", ] script_names = [ "coconut", - "coconut-" + VERSION_TAG.split("-", 1)[0], ("coconut-py2" if PY2 else "coconut-py3"), "coconut-py" + str(sys.version_info[0]) + str(sys.version_info[1]), ("coconut-develop" if DEVELOP else "coconut-release"), +] + [ + "coconut-v" + ".".join(version_tuple[:i]) for i in range(1, len(version_tuple) + 1) ] #----------------------------------------------------------------------------------------------------------------------- @@ -216,17 +236,20 @@ def fixpath(path): hash_sep = "\x00" specific_targets = ("2", "27", "3", "33", "35", "36") -targets = ("",) + specific_targets pseudo_targets = { "26": "2", "32": "3", "34": "33", } -sys_target = str(sys.version_info[0]) + str(sys.version_info[1]) -if sys_target in pseudo_targets: - pseudo_targets["sys"] = pseudo_targets[sys_target] + +targets = ("",) + specific_targets +_sys_target = str(sys.version_info[0]) + str(sys.version_info[1]) +if _sys_target in pseudo_targets: + pseudo_targets["sys"] = pseudo_targets[_sys_target] +elif sys.version_info > get_target_info(specific_targets[-1]): + pseudo_targets["sys"] = specific_targets[-1] else: - pseudo_targets["sys"] = sys_target + pseudo_targets["sys"] = _sys_target default_encoding = "utf-8" @@ -379,12 +402,6 @@ def fixpath(path): info_tabulation = 18 # offset for tabulated info messages -version_long = "Version " + VERSION_STR + " running on Python " + sys.version.split()[0] -version_banner = "Coconut " + VERSION_STR -if DEVELOP: - version_tag = "develop" -else: - version_tag = VERSION_TAG tutorial_url = "http://coconut.readthedocs.io/en/" + version_tag + "/HELP.html" documentation_url = "http://coconut.readthedocs.io/en/" + version_tag + "/DOCS.html" @@ -403,7 +420,7 @@ def fixpath(path): # HIGHLIGHTER CONSTANTS: #----------------------------------------------------------------------------------------------------------------------- -shebang_regex = r'coconut(-run)?' +shebang_regex = r'coconut(?:-run)?' builtins = ( "reduce", @@ -482,6 +499,8 @@ def fixpath(path): all_keywords = keywords + const_vars + reserved_vars +conda_build_env_var = "CONDA_BUILD" + #----------------------------------------------------------------------------------------------------------------------- # DOCUMENTATION CONSTANTS: #----------------------------------------------------------------------------------------------------------------------- diff --git a/coconut/icoconut/root.py b/coconut/icoconut/root.py index 137318f18..95d09224b 100644 --- a/coconut/icoconut/root.py +++ b/coconut/icoconut/root.py @@ -30,6 +30,7 @@ tutorial_url, documentation_url, code_exts, + conda_build_env_var, ) from coconut.terminal import logger from coconut.compiler import Compiler @@ -44,9 +45,9 @@ from ipykernel.zmqshell import ZMQInteractiveShell except ImportError: LOAD_MODULE = False - if os.environ.get("CONDA_BUILD"): + if os.environ.get(conda_build_env_var): # conda tries to import coconut.icoconut as a test even when IPython isn't available - logger.warn("Detected CONDA_BUILD; skipping coconut.icoconut loading") + logger.warn("Detected " + conda_build_env_var + "; skipping coconut.icoconut loading") else: raise CoconutException("--ipython flag requires IPython library", extra="run 'pip install coconut[ipython]' to fix") diff --git a/coconut/root.py b/coconut/root.py index 59dea0877..bc75bf823 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -36,8 +36,6 @@ VERSION += "-post_dev" + str(int(DEVELOP)) __version__ = VERSION VERSION_STR = VERSION + " [" + VERSION_NAME + "]" -VERSION_TAG = "v" + VERSION -VERSION_STR_TAG = "v" + VERSION_STR PY2 = _coconut_sys.version_info < (3,) PY26 = _coconut_sys.version_info < (2, 7) diff --git a/conf.py b/conf.py index 15c92ce96..959ff5430 100644 --- a/conf.py +++ b/conf.py @@ -23,7 +23,11 @@ from coconut.root import * # NOQA -from coconut.constants import without_toc, with_toc +from coconut.constants import ( + without_toc, + with_toc, + version_str_tag, +) from recommonmark.parser import CommonMarkParser from sphinx_bootstrap_theme import get_html_theme_path @@ -51,7 +55,7 @@ copyright = "2015-2017, Evan Hubinger, licensed under Apache 2.0" author = "Evan Hubinger" version = VERSION -release = VERSION_STR_TAG +release = version_str_tag master_doc = "index" source_suffix = [".rst", ".md"] From f5bc8b09fa4f9bfe33c24fb9aefa4b322e034044 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 2 May 2017 18:04:12 -0700 Subject: [PATCH 150/176] Improves cli, reqs, conf --- Makefile | 9 ++++--- coconut/command/command.py | 10 ++++++-- coconut/compiler/util.py | 4 ++-- coconut/constants.py | 8 +++++-- coconut/requirements.py | 36 +++++++++++++++++----------- conf.py | 48 ++++++++++++++++++++++++++++++++++++-- 6 files changed, 90 insertions(+), 25 deletions(-) diff --git a/Makefile b/Makefile index 6009f6fa2..ef55a403f 100644 --- a/Makefile +++ b/Makefile @@ -18,12 +18,15 @@ format: dev test: pytest --strict -s tests -.PHONY: docs -docs: clean +.PHONY: sphinx +sphinx: clean sphinx-build -b html . ./docs - pushd ./docs; zip -r ./docs.zip ./*; popd rm -rf index.rst +.PHONY: docs +docs: sphinx + pushd ./docs; zip -r ./docs.zip ./*; popd + .PHONY: clean clean: rm -rf ./docs ./dist ./build ./tests/dest index.rst diff --git a/coconut/command/command.py b/coconut/command/command.py index 6de285048..2921640a1 100644 --- a/coconut/command/command.py +++ b/coconut/command/command.py @@ -41,6 +41,7 @@ icoconut_kernel_dirs, minimum_recursion_limit, stub_dir, + exit_char, ) from coconut.command.util import ( openfile, @@ -431,14 +432,19 @@ def hashashof(self, destpath, code, package): def get_input(self, more=False): """Prompts for code input.""" + received = None try: - return self.prompt.input(more) + received = self.prompt.input(more) except KeyboardInterrupt: printerr("\nKeyboardInterrupt") except EOFError: print() self.exit_runner() - return None + else: + if received.startswith(exit_char): + self.exit_runner() + received = None + return received def start_running(self): """Starts running the Runner.""" diff --git a/coconut/compiler/util.py b/coconut/compiler/util.py index eae4106b7..09ecb429f 100644 --- a/coconut/compiler/util.py +++ b/coconut/compiler/util.py @@ -162,9 +162,9 @@ def split_leading_indent(line, max_indents=None): indent = "" while line.lstrip() != line or ( (max_indents is None or max_indents > 0) - and (line.startswith(openindent) or line.startswith(closeindent)) + and line.startswith((openindent, closeindent)) ): - if max_indents is not None and (line.startswith(openindent) or line.startswith(closeindent)): + if max_indents is not None and line.startswith((openindent, closeindent)): max_indents -= 1 indent += line[0] line = line[1:] diff --git a/coconut/constants.py b/coconut/constants.py index c4c250173..fbc59dea2 100644 --- a/coconut/constants.py +++ b/coconut/constants.py @@ -402,8 +402,10 @@ def get_target_info(target): info_tabulation = 18 # offset for tabulated info messages -tutorial_url = "http://coconut.readthedocs.io/en/" + version_tag + "/HELP.html" -documentation_url = "http://coconut.readthedocs.io/en/" + version_tag + "/DOCS.html" + +base_url = "http://coconut.readthedocs.io/en/" + version_tag +tutorial_url = base_url + "/HELP.html" +documentation_url = base_url + "/DOCS.html" base_dir = os.path.dirname(os.path.abspath(fixpath(__file__))) @@ -416,6 +418,8 @@ def get_target_info(target): stub_dir = os.path.join(base_dir, "stubs") +exit_char = "\x1a" + #----------------------------------------------------------------------------------------------------------------------- # HIGHLIGHTER CONSTANTS: #----------------------------------------------------------------------------------------------------------------------- diff --git a/coconut/requirements.py b/coconut/requirements.py index 8fbdbc06b..3c9f946ce 100644 --- a/coconut/requirements.py +++ b/coconut/requirements.py @@ -19,7 +19,6 @@ import sys import platform -import os import setuptools @@ -113,18 +112,18 @@ def add_version_reqs(modern=True): raise RuntimeError("bdist_wheel not supported for setuptools versions < 18 (run 'pip install --upgrade setuptools' to fix)") add_version_reqs(modern=False) else: - add_version_reqs(modern="CONDA_PREFIX" not in os.environ) + add_version_reqs() #----------------------------------------------------------------------------------------------------------------------- # MAIN: #----------------------------------------------------------------------------------------------------------------------- -def latest_version(req): - """Get the latest version of req from PyPI.""" +def all_versions(req): + """Get all versions of req from PyPI.""" import requests url = "https://pypi.python.org/pypi/" + req + "/json" - return requests.get(url).json()["info"]["version"] + return tuple(requests.get(url).json()["releases"].keys()) def ver_tuple(ver_str): @@ -139,18 +138,27 @@ def ver_tuple(ver_str): return tuple(out) +def newer(new_ver, old_ver): + """Determines if the first version tuple is newer than the second.""" + for n, o in zip(new_ver, old_ver): + if not isinstance(n, int): + o = str(o) + if o < n: + return True + if o > n: + return False + return False + + def print_new_versions(): """Prints new requirement versions.""" for req in everything_in(all_reqs): - new_str = latest_version(req) - new_ver = ver_tuple(new_str) - updated = False - for i, x in enumerate(req_vers[req]): - if len(new_ver) <= i or x != new_ver[i]: - updated = True - break - if updated: - print(req + ": " + req_str(req_vers[req]) + " -> " + new_str) + new_versions = [] + for ver_str in all_versions(req): + if newer(ver_tuple(ver_str), req_vers[req]): + new_versions.append(ver_str) + if new_versions: + print(req + ": " + req_str(req_vers[req]) + " -> " + ", ".join(new_versions)) if __name__ == "__main__": diff --git a/conf.py b/conf.py index 959ff5430..2b37cc367 100644 --- a/conf.py +++ b/conf.py @@ -27,10 +27,12 @@ without_toc, with_toc, version_str_tag, + base_url, ) -from recommonmark.parser import CommonMarkParser from sphinx_bootstrap_theme import get_html_theme_path +from recommonmark.parser import CommonMarkParser +from recommonmark.transform import AutoStructify #----------------------------------------------------------------------------------------------------------------------- # README: @@ -58,8 +60,50 @@ release = version_str_tag master_doc = "index" +exclude_patterns = ["README.*"] + source_suffix = [".rst", ".md"] source_parsers = { ".md": CommonMarkParser } -exclude_patterns = ["README.*"] + +default_role = "code" + +#----------------------------------------------------------------------------------------------------------------------- +# SETUP: +#----------------------------------------------------------------------------------------------------------------------- + + +def resolve_url(url): + """Automatically replace document references.""" + if url.startswith(("http://", "https://", "mailto:")): + return url + else: + return base_url + "/" + os.path.basename(url) + + +class PatchedAutoStructify(AutoStructify, object): + """AutoStructify by default can't handle contents directives.""" + + def patched_nested_parse(self, *args, **kwargs): + """Sets match_titles then calls stored_nested_parse.""" + kwargs["match_titles"] = True + return self.stored_nested_parse(*args, **kwargs) + + def auto_code_block(self, *args, **kwargs): + """Modified auto_code_block that patches nested_parse.""" + self.stored_nested_parse = self.state_machine.state.nested_parse + self.state_machine.state.nested_parse = self.patched_nested_parse + try: + return super(PatchedAutoStructify, self).auto_code_block(*args, **kwargs) + finally: + self.state_machine.state.nested_parse = self.stored_nested_parse + + +def setup(app): + app.add_config_value("recommonmark_config", { + "enable_auto_toc_tree": False, + "enable_inline_math": False, + "url_resolver": resolve_url, + }, True) + app.add_transform(PatchedAutoStructify) From 9a423b32e381f2d8854b33f6de3100a5d228f770 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 2 May 2017 18:05:14 -0700 Subject: [PATCH 151/176] Make sweeping docs changes --- DOCS.md | 92 +++++---------------------------------------------------- FAQ.md | 36 ++++++++-------------- HELP.md | 47 +++++------------------------ 3 files changed, 26 insertions(+), 149 deletions(-) diff --git a/DOCS.md b/DOCS.md index b39c18644..5c3926f1a 100644 --- a/DOCS.md +++ b/DOCS.md @@ -1,92 +1,14 @@ # Coconut Documentation - - -1. [Overview](#overview) -1. [Compilation](#compilation) - 1. [Installation](#installation) - 1. [Usage](#usage) - 1. [Positional Arguments](#positional-arguments) - 1. [Optional Arguments](#optional-arguments) - 1. [Coconut Scripts](#coconut-scripts) - 1. [Naming Source Files](#naming-source-files) - 1. [Compilation Modes](#compilation-modes) - 1. [Compatible Python Versions](#compatible-python-versions) - 1. [Allowable Targets](#allowable-targets) - 1. [`strict` Mode](#strict-mode) - 1. [IPython/ Jupyter Support](#ipython-jupyter-support) - 1. [Kernel](#kernel) - 1. [Extension](#extension) - 1. [MyPy Integration](#mypy-integration) -1. [Operators](#operators) - 1. [Lambdas](#lambdas) - 1. [Partial Application](#partial-application) - 1. [Pipeline](#pipeline) - 1. [Compose](#compose) - 1. [Chain](#chain) - 1. [Iterator Slicing](#iterator-slicing) - 1. [Unicode Alternatives](#unicode-alternatives) -1. [Keywords](#keywords) - 1. [`data`](#data) - 1. [`match`](#match) - 1. [`case`](#case) - 1. [Backslash-Escaping](#backslash-escaping) - 1. [Reserved Variables](#reserved-variables) -1. [Expressions](#expressions) - 1. [Statement Lambdas](#statement-lambdas) - 1. [Lazy Lists](#lazy-lists) - 1. [Implicit Partial Application](#implicit-partial-application) - 1. [Set Literals](#set-literals) - 1. [Imaginary Literals](#imaginary-literals) - 1. [Underscore Separators](#underscore-separators) -1. [Dotted Function Definition](#dotted-function-definition) -1. [Function Definition](#function-definition) - 1. [Tail Call Optimization](#tail-call-optimization) - 1. [Operator Functions](#operator-functions) - 1. [Assignment Functions](#assignment-functions) - 1. [Pattern-Matching Functions](#pattern-matching-functions) - 1. [Infix Functions](#infix-functions) -1. [Statements](#statements) - 1. [Destructuring Assignment](#destructuring-assignment) - 1. [Decorators](#decorators) - 1. [`else` Statements](#else-statements) - 1. [`except` Statements](#except-statements) - 1. [Implicit `pass`](#implicit-pass) - 1. [In-line `global` And `nonlocal` Assignment](#in-line-global-and-nonlocal-assignment) - 1. [Code Passthrough](#code-passthrough) -1. [Built-Ins](#built-ins) - 1. [Enhanced Built-Ins](#enhanced-built-ins) - 1. [`addpattern`](#addpattern) - 1. [`prepattern`](#prepattern) - 1. [`reduce`](#reduce) - 1. [`takewhile`](#takewhile) - 1. [`dropwhile`](#dropwhile) - 1. [`tee`](#tee) - 1. [`consume`](#consume) - 1. [`count`](#count) - 1. [`datamaker`](#datamaker) - 1. [`fmap`](#fmap) - 1. [`recursive_iterator`](#recursiveiterator) - 1. [`parallel_map`](#parallelmap) - 1. [`concurrent_map`](#concurrentmap) - 1. [`MatchError`](#matcherror) -1. [Coconut Utilities](#coconut-utilities) - 1. [Syntax Highlighting](#syntax-highlighting) - 1. [SublimeText](#sublimetext) - 1. [Pygments](#pygments) - 1. [`coconut.__coconut__`](#coconutcoconut) - 1. [`coconut.convenience`](#coconutconvenience) - 1. [`parse`](#parse) - 1. [`setup`](#setup) - 1. [`cmd`](#cmd) - 1. [`version`](#version) - 1. [`CoconutException`](#coconutexception) - - +```eval_rst +.. contents:: + :local: + :depth: 2 +``` ## Overview -This documentation covers all the technical details of the [Coconut Programming Language](http://evhub.github.io/coconut/), and is intended as a reference specification, not a tutorialized introduction. For a full introduction and tutorial of Coconut, see [the tutorial](http://coconut.readthedocs.io/en/master/HELP.html). +This documentation covers all the technical details of the [Coconut Programming Language](http://evhub.github.io/coconut/), and is intended as a reference specification, not a tutorialized introduction. For a full introduction and tutorial of Coconut, see [the tutorial](HELP.html). Coconut is a variant of [Python](https://www.python.org/) built for **simple, elegant, Pythonic functional programming**. Coconut syntax is a strict superset of Python 3 syntax. That means users familiar with Python will already be familiar with most of Coconut. @@ -112,7 +34,7 @@ Alternatively, if you want to test out Coconut's latest and greatest, enter ``` pip install coconut-develop ``` -which will install the most recent working [development build](https://github.com/evhub/coconut/tree/develop) (optional dependency installation is also supported in the same manner as above if you want). For more information on the current development build, check out the [development version of this documentation](http://coconut.readthedocs.org/en/develop/DOCS.html). Be warned: `coconut-develop` is likely to be unstable—if you find a bug, please report it by [creating a new issue](https://github.com/evhub/coconut/issues/new). +which will install the most recent working [development build](https://github.com/evhub/coconut/tree/develop) (optional dependency installation is also supported in the same manner as above if you want). For more information on the current development build, check out the [development version of this documentation](DOCS.html). Be warned: `coconut-develop` is likely to be unstable—if you find a bug, please report it by [creating a new issue](https://github.com/evhub/coconut/issues/new). ### Usage diff --git a/FAQ.md b/FAQ.md index 39c095889..78d4638ed 100644 --- a/FAQ.md +++ b/FAQ.md @@ -1,23 +1,11 @@ -# Coconut Frequently Asked Questions - - - -1. [Can I use Python modules from Coconut and Coconut modules from Python?](#can-i-use-python-modules-from-coconut-and-coconut-modules-from-python) -1. [What versions of Python does Coconut support?](#what-versions-of-python-does-coconut-support) -1. [I saw that Coconut was recently updated. Where is the change log?](#i-saw-that-coconut-was-recently-updated-where-is-the-change-log) -1. [Help! I tried to write a recursive iterator and my Python segfaulted!](#help-i-tried-to-write-a-recursive-iterator-and-my-python-segfaulted) -1. [If I'm already perfectly happy with Python, why should I learn Coconut?](#if-im-already-perfectly-happy-with-python-why-should-i-learn-coconut) -1. [How will I be able to debug my Python if I'm not the one writing it?](#how-will-i-be-able-to-debug-my-python-if-im-not-the-one-writing-it) -1. [I don't like functional programming, should I still learn Coconut?](#i-dont-like-functional-programming-should-i-still-learn-coconut) -1. [I don't know functional programming, should I still learn Coconut?](#i-dont-know-functional-programming-should-i-still-learn-coconut) -1. [I don't know Python very well, should I still learn Coconut?](#i-dont-know-python-very-well-should-i-still-learn-coconut) -1. [Why isn't Coconut purely functional?](#why-isnt-coconut-purely-functional) -1. [Won't a transpiled language like Coconut be bad for the Python community?](#wont-a-transpiled-language-like-coconut-be-bad-for-the-python-community) -1. [I want to contribute to Coconut, how do I get started?](#i-want-to-contribute-to-coconut-how-do-i-get-started) -1. [Why the name Coconut?](#why-the-name-coconut) -1. [Who developed Coconut?](#who-developed-coconut) - - +# Coconut FAQ + +## Frequently Asked Questions + +```eval_rst +.. contents:: + :local: +``` ### Can I use Python modules from Coconut and Coconut modules from Python? @@ -25,7 +13,7 @@ Yes and yes! Coconut compiles to Python, so Coconut modules are accessible from ### What versions of Python does Coconut support? -Coconut supports any Python version `>= 2.6` on the `2.x` branch or `>= 3.2` on the `3.x` branch. See [compatible Python versions](http://coconut.readthedocs.io/en/master/DOCS.html#compatible-python-versions) for more information. +Coconut supports any Python version `>= 2.6` on the `2.x` branch or `>= 3.2` on the `3.x` branch. See [compatible Python versions](DOCS.html#compatible-python-versions) for more information. ### I saw that Coconut was recently updated. Where is the change log? @@ -33,7 +21,7 @@ Information on every Coconut release is chronicled on the [GitHub releases page] ### Help! I tried to write a recursive iterator and my Python segfaulted! -No problem—just use Coconut's [`recursive_iterator`](http://coconut.readthedocs.io/en/master/DOCS.html#recursive-iterator) decorator and you should be fine. This is a [known Python issue](http://bugs.python.org/issue14010) but `recursive_iterator` will fix it for you. +No problem—just use Coconut's [`recursive_iterator`](DOCS.html#recursive-iterator) decorator and you should be fine. This is a [known Python issue](http://bugs.python.org/issue14010) but `recursive_iterator` will fix it for you. ### If I'm already perfectly happy with Python, why should I learn Coconut? @@ -49,7 +37,7 @@ Definitely! While Coconut is great for functional programming, it also has a bun ### I don't know functional programming, should I still learn Coconut? -Yes, absolutely! Coconut's [tutorial](http://coconut.readthedocs.io/en/master/HELP.html) assumes absolutely no prior knowledge of functional programming, only Python. Because Coconut is not a purely functional programming language, and all valid Python is valid Coconut, Coconut is a great introduction to functional programming. If you learn Coconut, you'll be able to try out a new functional style of programming without having to abandon all the Python you already know and love. +Yes, absolutely! Coconut's [tutorial](HELP.html) assumes absolutely no prior knowledge of functional programming, only Python. Because Coconut is not a purely functional programming language, and all valid Python is valid Coconut, Coconut is a great introduction to functional programming. If you learn Coconut, you'll be able to try out a new functional style of programming without having to abandon all the Python you already know and love. ### I don't know Python very well, should I still learn Coconut? @@ -65,7 +53,7 @@ I certainly hope not! Unlike most transpiled languages, all valid Python is vali ### I want to contribute to Coconut, how do I get started? -That's great! Coconut is completely open-source, and new contributors are always welcome. Check out Coconut's [contributing guidelines](http://coconut.readthedocs.io/en/master/CONTRIBUTING.html) for more information. +That's great! Coconut is completely open-source, and new contributors are always welcome. Check out Coconut's [contributing guidelines](CONTRIBUTING.html) for more information. ### Why the name Coconut? diff --git a/HELP.md b/HELP.md index 9b5dca550..710abaac0 100644 --- a/HELP.md +++ b/HELP.md @@ -1,42 +1,9 @@ # Coconut Tutorial - - -1. [Introduction](#introduction) - 1. [Installation](#installation) -1. [Starting Out](#starting-out) - 1. [Using the Interpreter](#using-the-interpreter) - 1. [Using the Compiler](#using-the-compiler) - 1. [Using IPython/ Jupyter](#using-ipython-jupyter) - 1. [Case Studies](#case-studies) -1. [Case Study 1: `factorial`](#case-study-1-factorial) - 1. [Imperative Method](#imperative-method) - 1. [Recursive Method](#recursive-method) - 1. [Iterative Method](#iterative-method) - 1. [`addpattern` Method](#addpattern-method) -1. [Case Study 2: `quick_sort`](#case-study-2-quicksort) - 1. [Sorting a Sequence](#sorting-a-sequence) - 1. [Sorting an Iterator](#sorting-an-iterator) -1. [Case Study 3: `vector` Part I](#case-study-3-vector-part-i) - 1. [2-Vector](#2-vector) - 1. [n-Vector Constructor](#n-vector-constructor) - 1. [n-Vector Methods](#n-vector-methods) -1. [Case Study 4: `vector_field`](#case-study-4-vectorfield) - 1. [`diagonal_line`](#diagonalline) - 1. [`linearized_plane`](#linearizedplane) - 1. [`vector_field`](#vectorfield) - 1. [Applications](#applications) -1. [Case Study 5: `vector` Part II](#case-study-5-vector-part-ii) - 1. [`__truediv__`](#truediv) - 1. [`.unit`](#unit) - 1. [`.angle`](#angle) -1. [Filling in the Gaps](#filling-in-the-gaps) - 1. [Lazy Lists](#lazy-lists) - 1. [Function Composition](#function-composition) - 1. [Implicit Partials](#implicit-partials) - 1. [Further Reading](#further-reading) - - +```eval_rst +.. contents:: + :local: +``` ## Introduction @@ -117,7 +84,7 @@ hello, world! Of course, while being able to interpret Coconut code on-the-fly is a great thing, it wouldn't be very useful without the ability to write and compile larger programs. To that end, it's time to write our first Coconut program: "hello, world!" Coconut-style. -First, we're going to need to create a file to put our code into. The recommended file extension for Coconut source files is `.coco`, so let's create the new file `hello_world.coco`. After you do that, you should take the time now to set up your text editor to properly highlight Coconut code. For instructions on how to do that, see the documentation on [Coconut syntax highlighting](http://coconut.readthedocs.io/en/master/DOCS.html#syntax-highlighting). +First, we're going to need to create a file to put our code into. The recommended file extension for Coconut source files is `.coco`, so let's create the new file `hello_world.coco`. After you do that, you should take the time now to set up your text editor to properly highlight Coconut code. For instructions on how to do that, see the documentation on [Coconut syntax highlighting](DOCS.html#syntax-highlighting). Now let's put some code in our `hello_world.coco` file. Unlike in Python, where headers like ```coconut_python @@ -176,7 +143,7 @@ or equivalently, `--ipython` can be substituted for `--jupyter` in either comman Because Coconut is built to be fundamentally _useful_, the best way to demo it is to show it in action. To that end, the majority of this tutorial will be showing how to apply Coconut to solve particular problems, which we'll call case studies. -These case studies are not intended to provide a complete picture of all of Coconut's features. For that, see Coconut's comprehensive [documentation](http://coconut.readthedocs.io/en/master/DOCS.html). Instead, they are intended to show how Coconut can actually be used to solve practical programming problems. +These case studies are not intended to provide a complete picture of all of Coconut's features. For that, see Coconut's comprehensive [documentation](DOCS.html). Instead, they are intended to show how Coconut can actually be used to solve practical programming problems. ## Case Study 1: `factorial` @@ -1112,7 +1079,7 @@ iter$[] ### Further Reading -And that's it for this tutorial! But that's hardly it for Coconut. All of the features examined in this tutorial, as well as a bunch of others, are documented in detail in Coconut's comprehensive [documentation](http://coconut.readthedocs.io/en/master/DOCS.html). +And that's it for this tutorial! But that's hardly it for Coconut. All of the features examined in this tutorial, as well as a bunch of others, are documented in detail in Coconut's comprehensive [documentation](DOCS.html). Also, if you have any other questions not covered in this tutorial, feel free to ask around at Coconut's [Gitter](https://gitter.im/evhub/coconut), a GitHub-integrated chat room for Coconut developers. From 214ba80013f889060870f7d7f5fb74546dd1e7b7 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 2 May 2017 18:22:53 -0700 Subject: [PATCH 152/176] Update version checking, doc parsing --- coconut/requirements.py | 24 +++++++++++++++++++----- conf.py | 11 +---------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/coconut/requirements.py b/coconut/requirements.py index 3c9f946ce..9a38cac51 100644 --- a/coconut/requirements.py +++ b/coconut/requirements.py @@ -140,25 +140,39 @@ def ver_tuple(ver_str): def newer(new_ver, old_ver): """Determines if the first version tuple is newer than the second.""" + if old_ver == new_ver or old_ver + (0,) == new_ver: + return False for n, o in zip(new_ver, old_ver): if not isinstance(n, int): o = str(o) if o < n: return True - if o > n: + elif o > n: return False - return False + return None -def print_new_versions(): +def print_new_versions(strict=False): """Prints new requirement versions.""" + new_updates = [] + same_updates = [] for req in everything_in(all_reqs): new_versions = [] + same_versions = [] for ver_str in all_versions(req): - if newer(ver_tuple(ver_str), req_vers[req]): + comp = newer(ver_tuple(ver_str), req_vers[req]) + if comp is True: new_versions.append(ver_str) + elif not strict and comp is None: + same_versions.append(ver_str) + update_str = req + ": " + req_str(req_vers[req]) + " -> " + ", ".join( + new_versions + ["(" + v + ")" for v in same_versions] + ) if new_versions: - print(req + ": " + req_str(req_vers[req]) + " -> " + ", ".join(new_versions)) + new_updates.append(update_str) + elif same_versions: + same_updates.append(update_str) + print("\n".join(new_updates + same_updates)) if __name__ == "__main__": diff --git a/conf.py b/conf.py index 2b37cc367..28ceb6d01 100644 --- a/conf.py +++ b/conf.py @@ -27,7 +27,6 @@ without_toc, with_toc, version_str_tag, - base_url, ) from sphinx_bootstrap_theme import get_html_theme_path @@ -74,14 +73,6 @@ #----------------------------------------------------------------------------------------------------------------------- -def resolve_url(url): - """Automatically replace document references.""" - if url.startswith(("http://", "https://", "mailto:")): - return url - else: - return base_url + "/" + os.path.basename(url) - - class PatchedAutoStructify(AutoStructify, object): """AutoStructify by default can't handle contents directives.""" @@ -104,6 +95,6 @@ def setup(app): app.add_config_value("recommonmark_config", { "enable_auto_toc_tree": False, "enable_inline_math": False, - "url_resolver": resolve_url, + "enable_auto_doc_ref": False, }, True) app.add_transform(PatchedAutoStructify) From 37cb89759b039d8ea2b0862eff47085b1151221a Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 2 May 2017 18:37:18 -0700 Subject: [PATCH 153/176] Clean up after recent fixes --- coconut/constants.py | 6 ++++++ coconut/icoconut/root.py | 2 +- coconut/root.py | 2 +- conf.py | 18 ++++++++++-------- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/coconut/constants.py b/coconut/constants.py index fbc59dea2..2285ae924 100644 --- a/coconut/constants.py +++ b/coconut/constants.py @@ -524,3 +524,9 @@ def get_target_info(target): DOCS CONTRIBUTING """ + +project = "Coconut" +copyright = "2015-2017, Evan Hubinger, licensed under Apache 2.0" +author = "Evan Hubinger" + +highlight_language = "coconut" diff --git a/coconut/icoconut/root.py b/coconut/icoconut/root.py index 95d09224b..a20d55f01 100644 --- a/coconut/icoconut/root.py +++ b/coconut/icoconut/root.py @@ -47,7 +47,7 @@ LOAD_MODULE = False if os.environ.get(conda_build_env_var): # conda tries to import coconut.icoconut as a test even when IPython isn't available - logger.warn("Detected " + conda_build_env_var + "; skipping coconut.icoconut loading") + logger.warn("Missing IPython but detected " + conda_build_env_var + "; skipping coconut.icoconut loading") else: raise CoconutException("--ipython flag requires IPython library", extra="run 'pip install coconut[ipython]' to fix") diff --git a/coconut/root.py b/coconut/root.py index bc75bf823..5262a2496 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -26,7 +26,7 @@ VERSION = "1.2.2" VERSION_NAME = "Colonel" # False for release, int >= 1 for develop -DEVELOP = 15 +DEVELOP = 16 #----------------------------------------------------------------------------------------------------------------------- # CONSTANTS: diff --git a/conf.py b/conf.py index 28ceb6d01..8ca6177dd 100644 --- a/conf.py +++ b/conf.py @@ -24,9 +24,9 @@ from coconut.root import * # NOQA from coconut.constants import ( + version_str_tag, without_toc, with_toc, - version_str_tag, ) from sphinx_bootstrap_theme import get_html_theme_path @@ -47,17 +47,19 @@ # DEFINITIONS: #----------------------------------------------------------------------------------------------------------------------- -html_theme = "bootstrap" -html_theme_path = get_html_theme_path() - -highlight_language = "coconut" +from coconut.constants import ( # NOQA + project, + copyright, + author, + highlight_language, +) -project = "Coconut" -copyright = "2015-2017, Evan Hubinger, licensed under Apache 2.0" -author = "Evan Hubinger" version = VERSION release = version_str_tag +html_theme = "bootstrap" +html_theme_path = get_html_theme_path() + master_doc = "index" exclude_patterns = ["README.*"] From 530a730c2eec9338f3a3740eb242ed7c27c1399b Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 2 May 2017 18:39:04 -0700 Subject: [PATCH 154/176] Clean up spacing --- coconut/constants.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/coconut/constants.py b/coconut/constants.py index 2285ae924..d35680fea 100644 --- a/coconut/constants.py +++ b/coconut/constants.py @@ -43,14 +43,12 @@ def get_target_info(target): #----------------------------------------------------------------------------------------------------------------------- version_long = "Version " + VERSION_STR + " running on Python " + sys.version.split()[0] - version_banner = "Coconut " + VERSION_STR if DEVELOP: version_tag = "develop" else: version_tag = "v" + VERSION - version_str_tag = "v" + VERSION_STR version_tuple = VERSION.split(".") @@ -402,7 +400,6 @@ def get_target_info(target): info_tabulation = 18 # offset for tabulated info messages - base_url = "http://coconut.readthedocs.io/en/" + version_tag tutorial_url = base_url + "/HELP.html" documentation_url = base_url + "/DOCS.html" From 5dab0f27395bed586fad8b99fbe7bbd50fbc5249 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 2 May 2017 18:45:56 -0700 Subject: [PATCH 155/176] Improve documentation structure and formatting --- DOCS.md | 263 +++++++++++++++++++++++++++----------------------------- 1 file changed, 126 insertions(+), 137 deletions(-) diff --git a/DOCS.md b/DOCS.md index 5c3926f1a..64cccca55 100644 --- a/DOCS.md +++ b/DOCS.md @@ -175,6 +175,8 @@ If the `--strict` (or `-s`) flag is enabled, Coconut will throw errors on variou It is recommended that you use the `--strict` (or `-s`) flag if you are starting a new Coconut project, as it will help you write cleaner code. +## Coconut Utilities + ### IPython/ Jupyter Support If you prefer [IPython](http://ipython.org/) (the python kernel for the [Jupyter](http://jupyter.org/) framework) to the normal Python shell, Coconut can be used as a Jupyter kernel or IPython extension. @@ -207,6 +209,36 @@ Coconut even supports `--mypy` in the interpreter, which will intelligently scan _Note: Since [tail call optimization](#tail-call-optimization) prevents proper type-checking, `--mypy` implicitly disables it._ +### Syntax Highlighting + +The current options for Coconut syntax highlighting are: + +1. use **[SublimeText](https://www.sublimetext.com/)** (instructions below), +2. use an editor that supports **[Pygments](http://pygments.org/)** (instructions below), +3. use [`coconut.vim`](https://github.com/manicmaniac/coconut.vim), a third-party **[Vim](http://www.vim.org/)** highlighter, +4. use [`coconut-mode`](https://github.com/NickSeagull/coconut-mode), a third-party **[Emacs](https://www.gnu.org/software/emacs/)** highlighter, or +4. just treat Coconut as Python. + +Instructions on how to set up syntax highlighting for SublimeText and Pygments are included below. If one of the actual highlighters above doesn't work, however, it should be sufficient to set up your editor so it interprets all `.coco` (also `.coc` and `.coconut`, although `.coco` is the preferred extension) files as Python code, as this should highlight most of your code well enough. + +#### SublimeText + +Coconut syntax highlighting for SublimeText requires that [Package Control](https://packagecontrol.io/installation), the standard package manager for SublimeText, be installed. Once that is done, simply: + +1. open the SublimeText command palette by pressing `Ctrl+Shift+P`, +2. enter and select `Package Control: Install Package`, and +3. finally enter and select `Coconut`. + +To make sure everything is working properly, open a `.coco` file, and make sure `Coconut` appears in the bottom right-hand corner. If something else appears, like `Plain Text`, click on it, select `Open all with current extension as...` at the top of the resulting menu, and then select `Coconut`. + +#### Pygments + +The same `pip install coconut` command that installs the Coconut command-line utility will also install the `coconut` Pygments lexer. How to use this lexer depends on the Pygments-enabled application being used, but in general simply enter `coconut` as the language being highlighted and/or use a valid Coconut file extension (`.coco`, `.coc`, or `.coconut`) and Pygments should be able to figure it out. For example, this documentation is generated with [Sphinx](http://www.sphinx-doc.org/en/stable/), with the syntax highlighting you see created by adding the line +```coconut_python +highlight_language = "coconut" +``` +to Coconut's `conf.py`. + ## Operators ### Lambdas @@ -232,13 +264,13 @@ Note that functions created with lambda forms cannot contain statements or annot ##### Example -###### Coconut +**Coconut:** ```coconut dubsums = map((x, y) -> 2*(x+y), range(0, 10), range(10, 20)) dubsums |> list |> print ``` -###### Python +**Python:** ```coconut_python dubsums = map(lambda x, y: 2*(x+y), range(0, 10), range(10, 20)) print(list(dubsums)) @@ -272,13 +304,13 @@ The `partial` object is used for partial function application which “freezes ##### Example -###### Coconut +**Coconut:** ```coconut expnums = range(5) |> map$(pow$(?, 2)) expnums |> list |> print ``` -###### Python +**Python:** ```coconut_python # unlike this simple lambda, $ produces a pickleable object expnums = map(lambda x: pow(x, 2), range(5)) @@ -297,13 +329,13 @@ Coconut uses pipe operators for pipeline-style function application. All the ope ##### Example -###### Coconut +**Coconut:** ```coconut def sq(x) = x**2 (1, 2) |*> (+) |> sq |> print ``` -###### Python +**Python:** ```coconut_python import operator def sq(x): return x**2 @@ -316,12 +348,12 @@ Coconut uses the `..` operator for function composition. It has a precedence in- ##### Example -###### Coconut +**Coconut:** ```coconut fog = f..g ``` -###### Python +**Python:** ```coconut_python # unlike this simple lambda, .. produces a pickleable object fog = lambda *args, **kwargs: f(g(*args, **kwargs)) @@ -348,15 +380,14 @@ def chain(*iterables): ##### Example -###### Coconut +**Coconut:** ```coconut def N(n=0) = (n,) :: N(n+1) # no infinite loop because :: is lazy (range(-10, 0) :: N())$[5:15] |> list |> print ``` -###### Python - +**Python:** _Can't be done without a complicated iterator comprehension in place of the lazy chaining. See the compiled code for the Python syntax._ ### Iterator Slicing @@ -369,12 +400,12 @@ Coconut's iterator slicing is very similar to Python's `itertools.islice`, but u ##### Example -###### Coconut +**Coconut:** ```coconut map((x)->x*2, range(10**100))$[-1] |> print ``` -###### Python +**Python:** _Can't be done without a complicated iterator slicing function and inspection of custom objects. The necessary definitions in Python can be found in the Coconut header._ ### Unicode Alternatives @@ -444,7 +475,7 @@ Named tuple instances do not have per-instance dictionaries, so they are lightwe ##### Examples -###### Coconut +**Coconut:** ```coconut data vector2(x, y): def __abs__(self): @@ -492,7 +523,7 @@ data vector(*pts): ``` _Showcases starred `data` declaration._ -###### Python +**Python:** ```coconut_python import collections class vector2(collections.namedtuple("vector2", "x, y")): @@ -618,7 +649,7 @@ When checking whether or not an object can be matched against in a particular fa ##### Examples -###### Coconut +**Coconut:** ```coconut def factorial(value): match 0 in value: @@ -683,8 +714,7 @@ def sieve((||)) = [] ``` _Showcases how to match against iterators, namely that the empty iterator case (`(||)`) must come last, otherwise that case will exhaust the whole iterator before any other pattern has a chance to match against it._ -###### Python - +**Python:** _Can't be done without a long series of checks for each `match` statement. See the compiled code for the Python syntax._ ### `case` @@ -706,7 +736,7 @@ where `` is any `match` pattern, `` is the item to match against ##### Example -###### Coconut +**Coconut:** ```coconut def classify_sequence(value): out = "" # unlike with normal matches, only one of the patterns @@ -733,8 +763,7 @@ def classify_sequence(value): (1,1,1) |> classify_sequence |> print ``` -###### Python - +**Python:** _Can't be done without a long series of checks for each `match` statement. See the compiled code for the Python syntax._ ### Backslash-Escaping @@ -743,13 +772,13 @@ In Coconut, the keywords `data`, `match`, `case`, `async` (keyword in Python 3.5 ##### Example -###### Coconut +**Coconut:** ```coconut \data = 5 print(\data) ``` -###### Python +**Python:** ```coconut_python data = 5 print(data) @@ -775,12 +804,12 @@ Statement lambdas also support implicit lambda syntax, where when the arguments ##### Example -###### Coconut +**Coconut:** ```coconut L |> map$(def (x) -> y = 1 / x; y*(1 - y)) ``` -###### Python +**Python:** ```coconut_python def _lambda(x): y = 1 / x @@ -800,12 +829,12 @@ Lazy lists, where sequences are only evaluated when their contents are requested ##### Example -###### Coconut +**Coconut:** ```coconut (| print("hello,"), print("world!") |) |> consume ``` -###### Python +**Python:** _Can't be done without a complicated iterator comprehension in place of the lazy list. See the compiled code for the Python syntax._ ### Implicit Partial Application @@ -824,13 +853,13 @@ iter$[] => # the equivalent of seq[] for iterators ##### Example -###### Coconut +**Coconut:** ```coconut 1 |> "123"[] mod$ <| 5 <| 3 ``` -###### Python +**Python:** ```coconut_python "123"[1] mod(5, 3) @@ -842,12 +871,12 @@ Coconut allows an optional `s` to be prepended in front of Python set literals. ##### Example -###### Coconut +**Coconut:** ```coconut empty_frozen_set = f{} ``` -###### Python +**Python:** ```coconut_python empty_frozen_set = frozenset() ``` @@ -869,12 +898,12 @@ An imaginary literal yields a complex number with a real part of 0.0. Complex nu ##### Example -###### Coconut +**Coconut:** ```coconut 3 + 4i |> abs |> print ``` -###### Python +**Python:** ```coconut_python print(abs(3 + 4j)) ``` @@ -885,12 +914,12 @@ Coconut allows for one underscore between digits and after base specifiers in nu ##### Example -###### Coconut +**Coconut:** ```coconut 10_000_000.0 ``` -###### Python +**Python:** ```coconut_python 10000000.0 ``` @@ -901,13 +930,13 @@ Coconut allows for function definition using a dotted name to assign a function ##### Example -###### Coconut +**Coconut:** ```coconut def MyClass.my_method(self): ... ``` -###### Python +**Python:** ```coconut_python def my_method(self): ... @@ -931,7 +960,7 @@ _Note: Tail call optimization and tail recursion elimination can be turned off b ##### Example -###### Coconut +**Coconut:** ```coconut # unlike in Python, this function will never hit a maximum recursion depth error def factorial(n, acc=1): @@ -954,8 +983,7 @@ def is_odd(n is int if n > 0) = is_even(n-1) ``` _Showcases tail call optimization._ -###### Python - +**Python:** _Can't be done without rewriting the function(s)._ ### Operator Functions @@ -1006,12 +1034,12 @@ A very common thing to do in functional programming is to make use of function v ##### Example -###### Coconut +**Coconut:** ```coconut (range(0, 5), range(5, 10)) |*> map$(+) |> list |> print ``` -###### Python +**Python:** ```coconut_python import operator print(list(map(operator.add, range(0, 5), range(5, 10)))) @@ -1039,13 +1067,13 @@ Coconut's Assignment function definition is as easy to write as assignment to a ##### Example -###### Coconut +**Coconut:** ```coconut def binexp(x) = 2**x 5 |> binexp |> print ``` -###### Python +**Python:** ```coconut_python def binexp(x): return 2**x print(binexp(5)) @@ -1066,7 +1094,7 @@ _Note: Pattern-matching function definition can be combined with assignment and/ ##### Example -###### Coconut +**Coconut:** ```coconut def last_two(_ + [a, b]): return a, b @@ -1077,8 +1105,7 @@ range(5) |> last_two |> print {"x":1, "y":2} |> xydict_to_xytuple |> print ``` -###### Python - +**Python:** _Can't be done without a long series of checks at the top of the function. See the compiled code for the Python syntax._ ### Infix Functions @@ -1100,13 +1127,13 @@ A common idiom in functional programming is to write functions that are intended ##### Example -###### Coconut +**Coconut:** ```coconut def a `mod` b = a % b (x `mod` 2) `print` ``` -###### Python +**Python:** ```coconut_python def mod(a, b): return a % b print(mod(x, 2)) @@ -1134,14 +1161,13 @@ If a destructuring assignment statement fails, then instead of continuing on as ##### Example -###### Coconut +**Coconut:** ```coconut _ + [a, b] = [0, 1, 2, 3] print(a, b) ``` -###### Python - +**Python:** _Can't be done without a long series of checks in place of the destructuring assignment statement. See the compiled code for the Python syntax._ ### Decorators @@ -1150,13 +1176,13 @@ Unlike Python, which only supports a single variable or function call in a decor ##### Example -###### Coconut +**Coconut:** ```coconut @ wrapper1 .. wrapper2 $(arg) def func(x) = x**2 ``` -###### Python +**Python:** ```coconut_python def wrapper(func): return wrapper1(wrapper2(arg, func)) @@ -1171,7 +1197,7 @@ Coconut supports the compound statements `try`, `if`, and `match` on the end of ##### Example -###### Coconut +**Coconut:** ```coconut if invalid(input_list): raise Exception() @@ -1181,7 +1207,7 @@ else: print(input_list) ``` -###### Python +**Python:** ```coconut_python from collections.abc import Sequence if invalid(input_list): @@ -1199,7 +1225,7 @@ Python 3 requires that if multiple exceptions are to be caught, they must be pla ##### Example -###### Coconut +**Coconut:** ```coconut try: unsafe_func(arg) @@ -1207,7 +1233,7 @@ except SyntaxError, ValueError as err: handle(err) ``` -###### Python +**Python:** ```coconut_python try: unsafe_func(arg) @@ -1221,14 +1247,14 @@ Coconut supports the simple `class name(base)` and `data name(args)` as aliases ##### Example -###### Coconut +**Coconut:** ```coconut data Empty data Leaf(item) data Node(left, right) ``` -###### Python +**Python:** ```coconut_python import collections class Empty(collections.namedtuple("Empty", "")): @@ -1245,12 +1271,12 @@ Coconut allows for `global` or `nonlocal` to precede assignment to a variable or ##### Example -###### Coconut +**Coconut:** ```coconut global state_a, state_b = 10, 100 ``` -###### Python +**Python:** ```coconut_python global state_a, state_b; state_a, state_b = 10, 100 ``` @@ -1261,13 +1287,13 @@ Coconut supports the ability to pass arbitrary code through the compiler without ##### Example -###### Coconut +**Coconut:** ```coconut \\cdef f(x): return x |> g ``` -###### Python +**Python:** ```coconut_python cdef f(x): return g(x) @@ -1287,13 +1313,13 @@ Coconut's `map`, `zip`, `filter`, `reversed`, and `enumerate` objects are enhanc ##### Example -###### Coconut +**Coconut:** ```coconut map((+), range(5), range(6)) |> len |> print range(10) |> filter$((x) -> x < 5) |> reversed |> tuple |> print ``` -###### Python +**Python:** _Can't be done without defining a custom `map` type. The full definition of `map` can be found in the Coconut header._ ### `addpattern` @@ -1314,7 +1340,7 @@ def addpattern(base_func): ##### Example -###### Coconut +**Coconut:** ``` def factorial(0) = 1 @@ -1322,7 +1348,7 @@ def factorial(0) = 1 def factorial(n) = n * factorial(n - 1) ``` -###### Python +**Python:** _Can't be done without a complicated decorator definition and a long series of checks for each pattern-matching. See the compiled code for the Python syntax._ ### `prepattern` @@ -1340,7 +1366,7 @@ def prepattern(base_func): ##### Example -###### Coconut +**Coconut:** ``` def factorial(n) = n * factorial(n - 1) @@ -1348,7 +1374,7 @@ def factorial(n) = n * factorial(n - 1) def factorial(0) = 1 ``` -###### Python +**Python:** _Can't be done without a complicated decorator definition and a long series of checks for each pattern-matching. See the compiled code for the Python syntax._ ### `reduce` @@ -1363,13 +1389,13 @@ Apply _function_ of two arguments cumulatively to the items of _sequence_, from ##### Example -###### Coconut +**Coconut:** ```coconut product = reduce$(*) range(1, 10) |> product |> print ``` -###### Python +**Python:** ```coconut_python import operator import functools @@ -1398,12 +1424,12 @@ def takewhile(predicate, iterable): ##### Example -###### Coconut +**Coconut:** ```coconut negatives = takewhile(numiter, (x) -> x<0) ``` -###### Python +**Python:** ```coconut_python import itertools negatives = itertools.takewhile(numiter, lambda x: x<0) @@ -1432,12 +1458,12 @@ def dropwhile(predicate, iterable): ##### Example -###### Coconut +**Coconut:** ```coconut positives = dropwhile(numiter, (x) -> x<0) ``` -###### Python +**Python:** ```coconut_python import itertools positives = itertools.dropwhile(numiter, lambda x: x<0) @@ -1471,13 +1497,13 @@ This itertool may require significant auxiliary storage (depending on how much t ##### Example -###### Coconut +**Coconut:** ```coconut original, temp = tee(original) sliced = temp$[5:] ``` -###### Python +**Python:** ```coconut_python import itertools original, temp = itertools.tee(original) @@ -1501,12 +1527,12 @@ In the process of lazily applying operations to iterators, eventually a point is ##### Example -###### Coconut +**Coconut:** ```coconut range(10) |> map$((x) -> x**2) |> map$(print) |> consume ``` -###### Python +**Python:** ```coconut_python collections.deque(map(print, map(lambda x: x**2, range(10))), maxlen=0) ``` @@ -1532,12 +1558,12 @@ def count(start=0, step=1): ##### Example -###### Coconut +**Coconut:** ```coconut count()$[10**100] |> print ``` -###### Python +**Python:** _Can't be done quickly without Coconut's iterator slicing, which requires many complicated pieces. The necessary definitions in Python can be found in the Coconut header._ ### `datamaker` @@ -1555,14 +1581,14 @@ def datamaker(data_type): ##### Example -###### Coconut +**Coconut:** ```coconut data Tuple(elems): def __new__(cls, *elems): return elems |> datamaker(cls) ``` -###### Python +**Python:** ```coconut_python import collections class Tuple(collections.namedtuple("Tuple", "elems")): @@ -1579,7 +1605,7 @@ In functional programming, `fmap(func, obj)` takes a data type `obj` and returns ##### Example -###### Coconut +**Coconut:** ```coconut [1, 2, 3] |> fmap$(x -> x+1) == [2, 3, 4] @@ -1590,7 +1616,7 @@ Just(3) |> fmap$(x -> x*2) == Just(6) Nothing() |> fmap$(x -> x*2) == Nothing() ``` -###### Python +**Python:** ```coconut_python list(map(lambda x: x+1, [1, 2, 3])) == [2, 3, 4] @@ -1628,14 +1654,13 @@ which will work just fine. ##### Example -###### Coconut +**Coconut:** ```coconut @recursive_iterator def fib() = (1, 2) :: map((+), fib(), fib()$[1:]) ``` -###### Python - +**Python:** _Can't be done without a long decorator definition. The full definition of the decorator in Python can be found in the Coconut header._ ### `parallel_map` @@ -1652,12 +1677,12 @@ Equivalent to `map(func, *iterables)` except _func_ is executed asynchronously a ##### Example -###### Coconut +**Coconut:** ```coconut parallel_map(pow$(2), range(100)) |> list |> print ``` -###### Python +**Python:** ```coconut_python import functools import concurrent.futures @@ -1677,12 +1702,12 @@ Equivalent to `map(func, *iterables)` except _func_ is executed asynchronously a ##### Example -###### Coconut +**Coconut:** ```coconut concurrent_map(get_data_for_user, get_all_users()) |> list |> print ``` -###### Python +**Python:** ```coconut_python import functools import concurrent.futures @@ -1694,56 +1719,20 @@ with concurrent.futures.ThreadPoolExecutor() as executor: A `MatchError` is raised when a [destructuring assignment](#destructuring-assignment) statement fails, and thus `MatchError` is provided as a built-in for catching those errors. `MatchError` objects support two attributes, `pattern`, which is a string describing the failed pattern, and `value`, which is the object that failed to match that pattern. -## Coconut Utilities - -### Syntax Highlighting - -The current options for Coconut syntax highlighting are: - -1. use **[SublimeText](https://www.sublimetext.com/)** (instructions below), -2. use an editor that supports **[Pygments](http://pygments.org/)** (instructions below), -3. use [`coconut.vim`](https://github.com/manicmaniac/coconut.vim), a third-party **[Vim](http://www.vim.org/)** highlighter, -4. use [`coconut-mode`](https://github.com/NickSeagull/coconut-mode), a third-party **[Emacs](https://www.gnu.org/software/emacs/)** highlighter, or -4. just treat Coconut as Python. - -Instructions on how to set up syntax highlighting for SublimeText and Pygments are included below. If one of the actual highlighters above doesn't work, however, it should be sufficient to set up your editor so it interprets all `.coco` (also `.coc` and `.coconut`, although `.coco` is the preferred extension) files as Python code, as this should highlight most of your code well enough. - -#### SublimeText - -Coconut syntax highlighting for SublimeText requires that [Package Control](https://packagecontrol.io/installation), the standard package manager for SublimeText, be installed. Once that is done, simply: - -1. open the SublimeText command palette by pressing `Ctrl+Shift+P`, -2. enter and select `Package Control: Install Package`, and -3. finally enter and select `Coconut`. - -To make sure everything is working properly, open a `.coco` file, and make sure `Coconut` appears in the bottom right-hand corner. If something else appears, like `Plain Text`, click on it, select `Open all with current extension as...` at the top of the resulting menu, and then select `Coconut`. - -#### Pygments - -The same `pip install coconut` command that installs the Coconut command-line utility will also install the `coconut` Pygments lexer. How to use this lexer depends on the Pygments-enabled application being used, but in general simply enter `coconut` as the language being highlighted and/or use a valid Coconut file extension (`.coco`, `.coc`, or `.coconut`) and Pygments should be able to figure it out. For example, this documentation is generated with [Sphinx](http://www.sphinx-doc.org/en/stable/), with the syntax highlighting you see created by adding the line -```coconut_python -highlight_language = "coconut" -``` -to Coconut's `conf.py`. - -### `coconut.__coconut__` +## `coconut.__coconut__` Module It is sometimes useful to be able to access Coconut built-ins from pure Python. To accomplish this, Coconut provides `coconut.__coconut__`, which behaves exactly like the `__coconut__.py` header file included when Coconut is compiled in package mode. -All Coconut built-ins are accessible from `coconut.__coconut__`. The recommended way to import them is to use `from coconut.__coconut__ import` and import whatever built-ins you'll be using. - -##### Example - -###### Python +All Coconut built-ins are accessible from `coconut.__coconut__`. The recommended way to import them is to use `from coconut.__coconut__ import` and import whatever built-ins you'll be using. For example: ```coconut_python from coconut.__coconut__ import parallel_map ``` -### `coconut.convenience` +## `coconut.convenience` It is sometimes useful to be able to use the Coconut compiler from code, instead of from the command line. The recommended way to do this is to use `from coconut.convenience import` and import whatever convenience functions you'll be using. Specifications of the different convenience functions are as follows. -#### `parse` +### `parse` **coconut.convenience.parse**(_code,_ **[**_mode_**]**) @@ -1785,7 +1774,7 @@ Each _mode_ has two components: what parser it uses, and what header it prepends * Can parse any Coconut code and allows leading whitespace. + header: none -#### `setup` +### `setup` **coconut.convenience.setup**(_target, strict, minify, line\_numbers, keep\_lines, no\_tco_**)** @@ -1798,13 +1787,13 @@ Each _mode_ has two components: what parser it uses, and what header it prepends - _keep\_lines_: `False` (default) or `True` - _no\_tco_: `False` (default) or `True` -#### `cmd` +### `cmd` **coconut.convenience.cmd**(_args_, **[**_interact_**]**) Executes the given _args_ as if they were fed to `coconut` on the command-line, with the exception that unless _interact_ is true or `-i` is passed, the interpreter will not be started. Additionally, since `parse` and `cmd` share the same convenience parsing object, any changes made to the parsing with `cmd` will work just as if they were made with `setup`. -#### `version` +### `version` **coconut.convenience.version**(**[**_which_**]**) @@ -1816,6 +1805,6 @@ Retrieves a string containing information about the Coconut version. The optiona - `"tag"`: the version tag used in GitHub and documentation URLs - `"-v"`: the full string printed by `coconut -v` -#### `CoconutException` +### `CoconutException` If an error is encountered in a convenience function, a `CoconutException` instance may be raised. `coconut.convenience.CoconutException` is provided to allow catching such errors. From cf2b0b3a70bb66f39fde085c60689f61240ee660 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 2 May 2017 18:49:30 -0700 Subject: [PATCH 156/176] Fix documentation errors --- DOCS.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/DOCS.md b/DOCS.md index 64cccca55..9587b2693 100644 --- a/DOCS.md +++ b/DOCS.md @@ -12,7 +12,7 @@ This documentation covers all the technical details of the [Coconut Programming Coconut is a variant of [Python](https://www.python.org/) built for **simple, elegant, Pythonic functional programming**. Coconut syntax is a strict superset of Python 3 syntax. That means users familiar with Python will already be familiar with most of Coconut. -The Coconut compiler turns Coconut code into Python code. The primary method of accessing the Coconut compiler is through the Coconut command-line utility, which also features an interpreter for real-time compilation. In addition to the command-line utility, Coconut also supports the use of IPython/ Jupyter notebooks. +The Coconut compiler turns Coconut code into Python code. The primary method of accessing the Coconut compiler is through the Coconut command-line utility, which also features an interpreter for real-time compilation. In addition to the command-line utility, Coconut also supports the use of IPython/Jupyter notebooks. While most of Coconut gets its inspiration simply from trying to make functional programming work in Python, additional inspiration came from [Haskell](https://www.haskell.org/), [CoffeeScript](http://coffeescript.org/), [F#](http://fsharp.org/), and [patterns.py](https://github.com/Suor/patterns). @@ -177,7 +177,7 @@ It is recommended that you use the `--strict` (or `-s`) flag if you are starting ## Coconut Utilities -### IPython/ Jupyter Support +### IPython/Jupyter Support If you prefer [IPython](http://ipython.org/) (the python kernel for the [Jupyter](http://jupyter.org/) framework) to the normal Python shell, Coconut can be used as a Jupyter kernel or IPython extension. @@ -1719,7 +1719,9 @@ with concurrent.futures.ThreadPoolExecutor() as executor: A `MatchError` is raised when a [destructuring assignment](#destructuring-assignment) statement fails, and thus `MatchError` is provided as a built-in for catching those errors. `MatchError` objects support two attributes, `pattern`, which is a string describing the failed pattern, and `value`, which is the object that failed to match that pattern. -## `coconut.__coconut__` Module +## Coconut Modules + +### `coconut.__coconut__` It is sometimes useful to be able to access Coconut built-ins from pure Python. To accomplish this, Coconut provides `coconut.__coconut__`, which behaves exactly like the `__coconut__.py` header file included when Coconut is compiled in package mode. @@ -1728,11 +1730,11 @@ All Coconut built-ins are accessible from `coconut.__coconut__`. The recommended from coconut.__coconut__ import parallel_map ``` -## `coconut.convenience` +### `coconut.convenience` It is sometimes useful to be able to use the Coconut compiler from code, instead of from the command line. The recommended way to do this is to use `from coconut.convenience import` and import whatever convenience functions you'll be using. Specifications of the different convenience functions are as follows. -### `parse` +#### `parse` **coconut.convenience.parse**(_code,_ **[**_mode_**]**) @@ -1774,7 +1776,7 @@ Each _mode_ has two components: what parser it uses, and what header it prepends * Can parse any Coconut code and allows leading whitespace. + header: none -### `setup` +#### `setup` **coconut.convenience.setup**(_target, strict, minify, line\_numbers, keep\_lines, no\_tco_**)** @@ -1787,13 +1789,13 @@ Each _mode_ has two components: what parser it uses, and what header it prepends - _keep\_lines_: `False` (default) or `True` - _no\_tco_: `False` (default) or `True` -### `cmd` +#### `cmd` **coconut.convenience.cmd**(_args_, **[**_interact_**]**) Executes the given _args_ as if they were fed to `coconut` on the command-line, with the exception that unless _interact_ is true or `-i` is passed, the interpreter will not be started. Additionally, since `parse` and `cmd` share the same convenience parsing object, any changes made to the parsing with `cmd` will work just as if they were made with `setup`. -### `version` +#### `version` **coconut.convenience.version**(**[**_which_**]**) @@ -1805,6 +1807,6 @@ Retrieves a string containing information about the Coconut version. The optiona - `"tag"`: the version tag used in GitHub and documentation URLs - `"-v"`: the full string printed by `coconut -v` -### `CoconutException` +#### `CoconutException` If an error is encountered in a convenience function, a `CoconutException` instance may be raised. `coconut.convenience.CoconutException` is provided to allow catching such errors. From 5b832a65cedb385c07c179f1e58f7bfd821471bc Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 2 May 2017 18:54:38 -0700 Subject: [PATCH 157/176] Fix dotted function definition docs --- DOCS.md | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/DOCS.md b/DOCS.md index 9587b2693..5da0d98c1 100644 --- a/DOCS.md +++ b/DOCS.md @@ -924,25 +924,6 @@ Coconut allows for one underscore between digits and after base specifiers in nu 10000000.0 ``` -## Dotted Function Definition - -Coconut allows for function definition using a dotted name to assign a function as a method of an object as specified in [PEP 542](https://www.python.org/dev/peps/pep-0542/). - -##### Example - -**Coconut:** -```coconut -def MyClass.my_method(self): - ... -``` - -**Python:** -```coconut_python -def my_method(self): - ... -MyClass.my_method = my_method -``` - ## Function Definition ### Tail Call Optimization @@ -1139,6 +1120,25 @@ def mod(a, b): return a % b print(mod(x, 2)) ``` +### Dotted Function Definition + +Coconut allows for function definition using a dotted name to assign a function as a method of an object as specified in [PEP 542](https://www.python.org/dev/peps/pep-0542/). + +##### Example + +**Coconut:** +```coconut +def MyClass.my_method(self): + ... +``` + +**Python:** +```coconut_python +def my_method(self): + ... +MyClass.my_method = my_method +``` + ## Statements ### Destructuring Assignment From aa443baaa4d3e75fbda3c93645975e3702ddb797 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 2 May 2017 18:56:35 -0700 Subject: [PATCH 158/176] Rework utilities docs --- DOCS.md | 62 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/DOCS.md b/DOCS.md index 5da0d98c1..a9561dd13 100644 --- a/DOCS.md +++ b/DOCS.md @@ -175,7 +175,37 @@ If the `--strict` (or `-s`) flag is enabled, Coconut will throw errors on variou It is recommended that you use the `--strict` (or `-s`) flag if you are starting a new Coconut project, as it will help you write cleaner code. -## Coconut Utilities +## Integrations + +### Syntax Highlighting + +The current options for Coconut syntax highlighting are: + +1. use **[SublimeText](https://www.sublimetext.com/)** (instructions below), +2. use an editor that supports **[Pygments](http://pygments.org/)** (instructions below), +3. use [`coconut.vim`](https://github.com/manicmaniac/coconut.vim), a third-party **[Vim](http://www.vim.org/)** highlighter, +4. use [`coconut-mode`](https://github.com/NickSeagull/coconut-mode), a third-party **[Emacs](https://www.gnu.org/software/emacs/)** highlighter, or +4. just treat Coconut as Python. + +Instructions on how to set up syntax highlighting for SublimeText and Pygments are included below. If one of the actual highlighters above doesn't work, however, it should be sufficient to set up your editor so it interprets all `.coco` (also `.coc` and `.coconut`, although `.coco` is the preferred extension) files as Python code, as this should highlight most of your code well enough. + +#### SublimeText + +Coconut syntax highlighting for SublimeText requires that [Package Control](https://packagecontrol.io/installation), the standard package manager for SublimeText, be installed. Once that is done, simply: + +1. open the SublimeText command palette by pressing `Ctrl+Shift+P`, +2. enter and select `Package Control: Install Package`, and +3. finally enter and select `Coconut`. + +To make sure everything is working properly, open a `.coco` file, and make sure `Coconut` appears in the bottom right-hand corner. If something else appears, like `Plain Text`, click on it, select `Open all with current extension as...` at the top of the resulting menu, and then select `Coconut`. + +#### Pygments + +The same `pip install coconut` command that installs the Coconut command-line utility will also install the `coconut` Pygments lexer. How to use this lexer depends on the Pygments-enabled application being used, but in general simply enter `coconut` as the language being highlighted and/or use a valid Coconut file extension (`.coco`, `.coc`, or `.coconut`) and Pygments should be able to figure it out. For example, this documentation is generated with [Sphinx](http://www.sphinx-doc.org/en/stable/), with the syntax highlighting you see created by adding the line +```coconut_python +highlight_language = "coconut" +``` +to Coconut's `conf.py`. ### IPython/Jupyter Support @@ -209,36 +239,6 @@ Coconut even supports `--mypy` in the interpreter, which will intelligently scan _Note: Since [tail call optimization](#tail-call-optimization) prevents proper type-checking, `--mypy` implicitly disables it._ -### Syntax Highlighting - -The current options for Coconut syntax highlighting are: - -1. use **[SublimeText](https://www.sublimetext.com/)** (instructions below), -2. use an editor that supports **[Pygments](http://pygments.org/)** (instructions below), -3. use [`coconut.vim`](https://github.com/manicmaniac/coconut.vim), a third-party **[Vim](http://www.vim.org/)** highlighter, -4. use [`coconut-mode`](https://github.com/NickSeagull/coconut-mode), a third-party **[Emacs](https://www.gnu.org/software/emacs/)** highlighter, or -4. just treat Coconut as Python. - -Instructions on how to set up syntax highlighting for SublimeText and Pygments are included below. If one of the actual highlighters above doesn't work, however, it should be sufficient to set up your editor so it interprets all `.coco` (also `.coc` and `.coconut`, although `.coco` is the preferred extension) files as Python code, as this should highlight most of your code well enough. - -#### SublimeText - -Coconut syntax highlighting for SublimeText requires that [Package Control](https://packagecontrol.io/installation), the standard package manager for SublimeText, be installed. Once that is done, simply: - -1. open the SublimeText command palette by pressing `Ctrl+Shift+P`, -2. enter and select `Package Control: Install Package`, and -3. finally enter and select `Coconut`. - -To make sure everything is working properly, open a `.coco` file, and make sure `Coconut` appears in the bottom right-hand corner. If something else appears, like `Plain Text`, click on it, select `Open all with current extension as...` at the top of the resulting menu, and then select `Coconut`. - -#### Pygments - -The same `pip install coconut` command that installs the Coconut command-line utility will also install the `coconut` Pygments lexer. How to use this lexer depends on the Pygments-enabled application being used, but in general simply enter `coconut` as the language being highlighted and/or use a valid Coconut file extension (`.coco`, `.coc`, or `.coconut`) and Pygments should be able to figure it out. For example, this documentation is generated with [Sphinx](http://www.sphinx-doc.org/en/stable/), with the syntax highlighting you see created by adding the line -```coconut_python -highlight_language = "coconut" -``` -to Coconut's `conf.py`. - ## Operators ### Lambdas From 4762f54bb6ebd4bdd40c2aa8d5f0c0f2cddf998c Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 2 May 2017 23:37:34 -0700 Subject: [PATCH 159/176] Prevent watcher double compilation Resolves #189. --- coconut/command/command.py | 30 ++++++++++++++++-------------- coconut/command/watch.py | 13 ++++++++++++- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/coconut/command/command.py b/coconut/command/command.py index 2921640a1..859e0902c 100644 --- a/coconut/command/command.py +++ b/coconut/command/command.py @@ -259,7 +259,7 @@ def handling_exceptions(self): traceback.print_exc() self.register_error(errmsg=err.__class__.__name__) - def compile_path(self, path, write=True, package=None, run=False, force=False): + def compile_path(self, path, write=True, package=None, *args, **kwargs): """Compiles a path and returns paths to compiled files.""" path = fixpath(path) if write is not None and write is not True: @@ -267,7 +267,7 @@ def compile_path(self, path, write=True, package=None, run=False, force=False): if os.path.isfile(path): if package is None: package = False - destpath = self.compile_file(path, write, package, run, force) + destpath = self.compile_file(path, write, package, *args, **kwargs) if destpath is None: return [] else: @@ -275,11 +275,11 @@ def compile_path(self, path, write=True, package=None, run=False, force=False): elif os.path.isdir(path): if package is None: package = True - return self.compile_folder(path, write, package, run, force) + return self.compile_folder(path, write, package, *args, **kwargs) else: raise CoconutException("could not find source path", path) - def compile_folder(self, directory, write=True, package=True, run=False, force=False): + def compile_folder(self, directory, write=True, package=True, *args, **kwargs): """Compiles a directory and returns paths to compiled files.""" filepaths = [] for dirpath, dirnames, filenames in os.walk(directory): @@ -289,7 +289,7 @@ def compile_folder(self, directory, write=True, package=True, run=False, force=F writedir = os.path.join(write, os.path.relpath(dirpath, directory)) for filename in filenames: if os.path.splitext(filename)[1] in code_exts: - destpath = self.compile_file(os.path.join(dirpath, filename), writedir, package, run, force) + destpath = self.compile_file(os.path.join(dirpath, filename), writedir, package, *args, **kwargs) if destpath is not None: filepaths.append(destpath) for name in dirnames[:]: @@ -299,7 +299,7 @@ def compile_folder(self, directory, write=True, package=True, run=False, force=F dirnames.remove(name) # directories removed from dirnames won't appear in further os.walk iteration return filepaths - def compile_file(self, filepath, write=True, package=False, run=False, force=False): + def compile_file(self, filepath, write=True, package=False, *args, **kwargs): """Compiles a file and returns the compiled file's path.""" if write is None: destpath = None @@ -314,13 +314,11 @@ def compile_file(self, filepath, write=True, package=False, run=False, force=Fal destpath = fixpath(base + ext) if filepath == destpath: raise CoconutException("cannot compile " + showpath(filepath) + " to itself", extra="incorrect file extension") - self.compile(filepath, destpath, package, run, force) + self.compile(filepath, destpath, package, *args, **kwargs) return destpath - def compile(self, codepath, destpath=None, package=False, run=False, force=False): + def compile(self, codepath, destpath=None, package=False, run=False, force=False, show_unchanged=True): """Compiles a source Coconut file to a destination Python file.""" - logger.show_tabulated("Compiling", showpath(codepath), "...") - with openfile(codepath, "r") as opened: code = readfile(opened) @@ -333,13 +331,15 @@ def compile(self, codepath, destpath=None, package=False, run=False, force=False foundhash = None if force else self.hashashof(destpath, code, package) if foundhash: - logger.show_tabulated("Left unchanged", showpath(destpath), "(pass --force to override).") + if show_unchanged: + logger.show_tabulated("Left unchanged", showpath(destpath), "(pass --force to override).") if self.show: print(foundhash) if run: self.execute_file(destpath) else: + logger.show_tabulated("Compiling", showpath(codepath), "...") if package is True: compile_method = "parse_package" @@ -350,7 +350,7 @@ def compile(self, codepath, destpath=None, package=False, run=False, force=False def callback(compiled): if destpath is None: - logger.show_tabulated("Finished", showpath(codepath), "without writing to file.") + logger.show_tabulated("Compiled", showpath(codepath), "without writing to file.") else: with openfile(destpath, "w") as opened: writefile(opened, compiled) @@ -622,16 +622,18 @@ def watch(self, source, write=True, package=None, run=False, force=False): def recompile(path): if os.path.isfile(path) and os.path.splitext(path)[1] in code_exts: with self.handling_exceptions(): - self.run_mypy(self.compile_path(path, write, package, run, force)) + self.run_mypy(self.compile_path(path, write, package, run, force, show_unchanged=False)) + watcher = RecompilationWatcher(recompile) observer = Observer() - observer.schedule(RecompilationWatcher(recompile), source, recursive=True) + observer.schedule(watcher, source, recursive=True) with self.running_jobs(): observer.start() try: while True: time.sleep(watch_interval) + watcher.keep_watching() except KeyboardInterrupt: logger.show("Got KeyboardInterrupt; stopping watcher.") finally: diff --git a/coconut/command/watch.py b/coconut/command/watch.py index 9ac134107..45ba6c492 100644 --- a/coconut/command/watch.py +++ b/coconut/command/watch.py @@ -34,9 +34,20 @@ class RecompilationWatcher(FileSystemEventHandler): + """Watcher that recompiles modified files.""" def __init__(self, recompile): + super(RecompilationWatcher, self).__init__() self.recompile = recompile + self.keep_watching() + + def keep_watching(self): + """Allows recompiling previously-compiled files.""" + self.saw = set() def on_modified(self, event): - self.recompile(event.src_path) + """Handle a file modified event.""" + path = event.src_path + if path not in self.saw: + self.saw.add(path) + self.recompile(path) From 690220cf051c7f6199381e6df44b3a6490ec3a26 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 2 May 2017 23:54:59 -0700 Subject: [PATCH 160/176] Add __ne__ definition to data blocks Resolves #244. --- HELP.md | 4 -- coconut/compiler/compiler.py | 79 ++++++++++++----------- tests/src/cocotest/agnostic/suite.coco | 9 +++ tests/src/cocotest/agnostic/tutorial.coco | 3 - 4 files changed, 50 insertions(+), 45 deletions(-) diff --git a/HELP.md b/HELP.md index 710abaac0..cc39e1202 100644 --- a/HELP.md +++ b/HELP.md @@ -555,7 +555,6 @@ Our next method will be equality. We're again going to use `data` pattern-matchi return True else: return False - def __ne__(self, other) = not self == other ``` The only new construct here is the use of `=self.pts` in the `match` statement. This construct is used to perform a check inside of the pattern-matching, making sure the `match` only succeeds if `other.pts == self.pts`. @@ -608,7 +607,6 @@ data vector(*pts): return True else: return False - def __ne__(self, other) = not self == other def __mul__(self, other): """Scalar multiplication and dot product.""" match vector(*other_pts) in other: @@ -822,7 +820,6 @@ data vector(*pts): return True else: return False - def __ne__(self, other) = not self == other def __mul__(self, other): """Scalar multiplication and dot product.""" match vector(*other_pts) in other: @@ -1011,7 +1008,6 @@ data vector(*pts): return True else: return False - def __ne__(self, other) = not self == other def __mul__(self, other): """Scalar multiplication and dot product.""" match vector(*other_pts) in other: diff --git a/coconut/compiler/compiler.py b/coconut/compiler/compiler.py index d677de7b0..007e1eaed 100644 --- a/coconut/compiler/compiler.py +++ b/coconut/compiler/compiler.py @@ -1058,31 +1058,34 @@ def data_handle(self, original, loc, tokens): else: base_args.append(arg) attr_str = " ".join(base_args) - extra_stmts = "__slots__ = ()\n" + extra_stmts = ( + '__slots__ = ()\n' + '__ne__ = object.__ne__\n' + ) if starred_arg is not None: attr_str += (" " if attr_str else "") + starred_arg if base_args: extra_stmts += r'''def __new__(_cls, {all_args}): - {oind}return _coconut.tuple.__new__(_cls, {base_args_tuple} + {starred_arg}) - {cind}@_coconut.classmethod - def _make(cls, iterable, {kwd_only}new=_coconut.tuple.__new__, len=_coconut.len): - {oind}result = new(cls, iterable) - if len(result) < {num_base_args}: - {oind}raise _coconut.TypeError("Expected at least 2 arguments, got %d" % len(result)) - {cind}return result - {cind}def _asdict(self): - {oind}return _coconut.OrderedDict((f, _coconut.getattr(self, f)) for f in self._fields) - {cind}def __repr__(self): - {oind}return "{name}({args_for_repr})".format(**self._asdict()) - {cind}def _replace(_self, **kwds): - {oind}result = _self._make(_coconut.tuple(_coconut.map(kwds.pop, {quoted_base_args_tuple}, _self)) + kwds.pop("{starred_arg}", self.{starred_arg})) - if kwds: - {oind}raise _coconut.ValueError("Got unexpected field names: %r" % kwds.keys()) - {cind}return result - {cind}@_coconut.property - def {starred_arg}(self): - {oind}return self[{num_base_args}:] - {cind}'''.format( + {oind}return _coconut.tuple.__new__(_cls, {base_args_tuple} + {starred_arg}) + {cind}@_coconut.classmethod + def _make(cls, iterable, {kwd_only}new=_coconut.tuple.__new__, len=_coconut.len): + {oind}result = new(cls, iterable) + if len(result) < {num_base_args}: + {oind}raise _coconut.TypeError("Expected at least 2 arguments, got %d" % len(result)) + {cind}return result + {cind}def _asdict(self): + {oind}return _coconut.OrderedDict((f, _coconut.getattr(self, f)) for f in self._fields) + {cind}def __repr__(self): + {oind}return "{name}({args_for_repr})".format(**self._asdict()) + {cind}def _replace(_self, **kwds): + {oind}result = _self._make(_coconut.tuple(_coconut.map(kwds.pop, {quoted_base_args_tuple}, _self)) + kwds.pop("{starred_arg}", self.{starred_arg})) + if kwds: + {oind}raise _coconut.ValueError("Got unexpected field names: %r" % kwds.keys()) + {cind}return result + {cind}@_coconut.property + def {starred_arg}(self): + {oind}return self[{num_base_args}:] + {cind}'''.format( oind=openindent, cind=closeindent, name=name, @@ -1096,23 +1099,23 @@ def {starred_arg}(self): ) else: extra_stmts += r'''def __new__(_cls, *{arg}): - {oind}return _coconut.tuple.__new__(_cls, {arg}) - {cind}@_coconut.classmethod - def _make(cls, iterable, {kwd_only}new=_coconut.tuple.__new__, len=None): - {oind}return new(cls, iterable) - {cind}def _asdict(self): - {oind}return _coconut.OrderedDict([("{arg}", self[:])]) - {cind}def __repr__(self): - {oind}return "{name}(*{arg}=%r)" % (self[:],) - {cind}def _replace(_self, **kwds): - {oind}result = self._make(kwds.pop("{arg}", _self)) - if kwds: - {oind}raise _coconut.ValueError("Got unexpected field names: %r" % kwds.keys()) - {cind}return result - {cind}@_coconut.property - def {arg}(self): - {oind}return self[:] - {cind}'''.format( + {oind}return _coconut.tuple.__new__(_cls, {arg}) + {cind}@_coconut.classmethod + def _make(cls, iterable, {kwd_only}new=_coconut.tuple.__new__, len=None): + {oind}return new(cls, iterable) + {cind}def _asdict(self): + {oind}return _coconut.OrderedDict([("{arg}", self[:])]) + {cind}def __repr__(self): + {oind}return "{name}(*{arg}=%r)" % (self[:],) + {cind}def _replace(_self, **kwds): + {oind}result = self._make(kwds.pop("{arg}", _self)) + if kwds: + {oind}raise _coconut.ValueError("Got unexpected field names: %r" % kwds.keys()) + {cind}return result + {cind}@_coconut.property + def {arg}(self): + {oind}return self[:] + {cind}'''.format( oind=openindent, cind=closeindent, name=name, diff --git a/tests/src/cocotest/agnostic/suite.coco b/tests/src/cocotest/agnostic/suite.coco index 06932e991..65ce1f419 100644 --- a/tests/src/cocotest/agnostic/suite.coco +++ b/tests/src/cocotest/agnostic/suite.coco @@ -70,6 +70,9 @@ def suite_test(): assert not (1, 2) |> vector(1, 2).__eq__ assert vector(vector(4, 3)) == vector(4, 3) assert not vector(4, 3) != vector(4, 3) + assert not vector(1, 2) == (1, 2) + assert not vector(2, 3) != vector(2, 3) + assert vector(1, 2) != (1, 2) assert triangle(3, 4, 5).is_right() assert (.)(triangle(3, 4, 5), "is_right") assert triangle(3, 4, 5) |> .is_right() @@ -124,6 +127,12 @@ def suite_test(): assert (5, 3) |*> mod == 2 == mod <*| (5, 3) assert Just(5) <| square <| plus1 == Just(26) assert Nothing() <| square <| plus1 == Nothing() + assert not Nothing() == () + assert not Nothing() != Nothing() + assert Nothing() != () + assert not Just(1) == (1,) + assert not Just(1) != Just(1) + assert Just(1) != (1,) assert head_tail([1,2,3]) == (1, [2,3]) assert init_last([1,2,3]) == ([1,2], 3) assert last_two([1,2,3]) == (2, 3) == last_two_([1,2,3]) diff --git a/tests/src/cocotest/agnostic/tutorial.coco b/tests/src/cocotest/agnostic/tutorial.coco index 81d7f6248..2964b6bc5 100644 --- a/tests/src/cocotest/agnostic/tutorial.coco +++ b/tests/src/cocotest/agnostic/tutorial.coco @@ -272,7 +272,6 @@ data vector(*pts): return True else: return False - def __ne__(self, other) = not self == other def __mul__(self, other): """Scalar multiplication and dot product.""" match vector(*other_pts) in other: @@ -347,7 +346,6 @@ data vector(*pts): return True else: return False - def __ne__(self, other) = not self == other def __mul__(self, other): """Scalar multiplication and dot product.""" match vector(*other_pts) in other: @@ -404,7 +402,6 @@ data vector(*pts): return True else: return False - def __ne__(self, other) = not self == other def __mul__(self, other): """Scalar multiplication and dot product.""" match vector(*other_pts) in other: From f439d3c5eb97a5e3fd463afccf4475465b6b272f Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Wed, 3 May 2017 00:09:08 -0700 Subject: [PATCH 161/176] Fix object reference --- coconut/compiler/compiler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coconut/compiler/compiler.py b/coconut/compiler/compiler.py index 007e1eaed..af5802f7f 100644 --- a/coconut/compiler/compiler.py +++ b/coconut/compiler/compiler.py @@ -1060,7 +1060,7 @@ def data_handle(self, original, loc, tokens): attr_str = " ".join(base_args) extra_stmts = ( '__slots__ = ()\n' - '__ne__ = object.__ne__\n' + '__ne__ = _coconut.object.__ne__\n' ) if starred_arg is not None: attr_str += (" " if attr_str else "") + starred_arg From 37332c44b5578c09710e7d5336cd8a9078ea4b4c Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Wed, 3 May 2017 00:16:54 -0700 Subject: [PATCH 162/176] Fix fib, bump develop version --- DOCS.md | 2 +- coconut/root.py | 2 +- tests/src/cocotest/agnostic/suite.coco | 1 + tests/src/cocotest/agnostic/util.coco | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/DOCS.md b/DOCS.md index a9561dd13..dca59ba0b 100644 --- a/DOCS.md +++ b/DOCS.md @@ -1657,7 +1657,7 @@ which will work just fine. **Coconut:** ```coconut @recursive_iterator -def fib() = (1, 2) :: map((+), fib(), fib()$[1:]) +def fib() = (1, 1) :: map((+), fib(), fib()$[1:]) ``` **Python:** diff --git a/coconut/root.py b/coconut/root.py index 5262a2496..5f6efd310 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -26,7 +26,7 @@ VERSION = "1.2.2" VERSION_NAME = "Colonel" # False for release, int >= 1 for develop -DEVELOP = 16 +DEVELOP = 17 #----------------------------------------------------------------------------------------------------------------------- # CONSTANTS: diff --git a/tests/src/cocotest/agnostic/suite.coco b/tests/src/cocotest/agnostic/suite.coco index 65ce1f419..91819c1ed 100644 --- a/tests/src/cocotest/agnostic/suite.coco +++ b/tests/src/cocotest/agnostic/suite.coco @@ -230,6 +230,7 @@ def suite_test(): assert pattern_abs(-4) == 4 == pattern_abs_(-4) assert vector(1, 2) |> (==)$(vector(1, 2)) assert vector(1, 2) |> .__eq__(other=vector(1, 2)) + assert fib()$[1:4] == (1, 2, 3) assert fib() |> takewhile$((i) -> i < 4000000 ) |> filter$((i) -> i % 2 == 0 ) |> sum == 4613732 assert loop([1,2])$[:4] |> list == [1, 2] * 2 assert (def -> mod)()(5, 3) == 2 diff --git a/tests/src/cocotest/agnostic/util.coco b/tests/src/cocotest/agnostic/util.coco index 07af64afb..611ae5bb3 100644 --- a/tests/src/cocotest/agnostic/util.coco +++ b/tests/src/cocotest/agnostic/util.coco @@ -529,7 +529,7 @@ def `pattern_abs_` (x) = x # Recursive iterator @recursive_iterator -def fib() = (1, 2) :: map((+), fib(), fib()$[1:]) +def fib() = (1, 1) :: map((+), fib(), fib()$[1:]) @recursive_iterator def loop(it) = it :: loop(it) From d7b1715b62ef18ff9d6cf2157e4b49ea0b5b00c4 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Wed, 3 May 2017 00:53:28 -0700 Subject: [PATCH 163/176] Improve documentation wording --- DOCS.md | 10 +++++----- HELP.md | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/DOCS.md b/DOCS.md index dca59ba0b..6d2493d48 100644 --- a/DOCS.md +++ b/DOCS.md @@ -8,9 +8,9 @@ ## Overview -This documentation covers all the technical details of the [Coconut Programming Language](http://evhub.github.io/coconut/), and is intended as a reference specification, not a tutorialized introduction. For a full introduction and tutorial of Coconut, see [the tutorial](HELP.html). +This documentation covers all the features of the [Coconut Programming Language](http://evhub.github.io/coconut/), and is intended as a reference/specification, not a tutorialized introduction. For a full introduction and tutorial of Coconut, see [the tutorial](HELP.html). -Coconut is a variant of [Python](https://www.python.org/) built for **simple, elegant, Pythonic functional programming**. Coconut syntax is a strict superset of Python 3 syntax. That means users familiar with Python will already be familiar with most of Coconut. +Coconut is a variant of [Python](https://www.python.org/) built for **simple, elegant, Pythonic functional programming**. Coconut syntax is a strict superset of Python 3 syntax. Thus, users familiar with Python will already be familiar with most of Coconut. The Coconut compiler turns Coconut code into Python code. The primary method of accessing the Coconut compiler is through the Coconut command-line utility, which also features an interpreter for real-time compilation. In addition to the command-line utility, Coconut also supports the use of IPython/Jupyter notebooks. @@ -641,9 +641,9 @@ pattern ::= ( - Head-Tail Splits (` + `): will match the beginning of the sequence against the ``, then bind the rest to ``, and make it the type of the construct used. - Init-Last Splits (` + `): exactly the same as head-tail splits, but on the end instead of the beginning of the sequence. - Head-Last Splits (` + + `): the combination of a head-tail and an init-last split. -- Iterator Splits (` :: `, or ``): will match the beginning of an iterable (`collections.abc.Iterable`) against the ``, then bind the rest to `` or check that the iterable is done. +- Iterator Splits (` :: `): will match the beginning of an iterable (`collections.abc.Iterable`) against the ``, then bind the rest to `` or check that the iterable is done. -_Note: Like [iterator slicing](#iterator-slicing), iterator and lazy list matching makes no guarantee that the original iterator matched against be preserved (to preserve the iterator, use Coconut's [`tee` function](#tee)._ +_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 [`tee` function](#tee)._ When checking whether or not an object can be matched against in a particular fashion, Coconut makes use of Python's abstract base classes. Therefore, to enable proper matching for a custom object, register it with the proper abstract base classes. @@ -786,7 +786,7 @@ print(data) ### Reserved Variables -It is illegal for a variable name to start with `_coconut`, as these variables are reserved for the compiler. +It is illegal for a variable name to start with `_coconut`, as these variables are reserved for the compiler. If interacting with such variables is necessary, use [code passthrough](#code-passthrough). ## Expressions diff --git a/HELP.md b/HELP.md index cc39e1202..641aa9065 100644 --- a/HELP.md +++ b/HELP.md @@ -9,7 +9,7 @@ Welcome to the tutorial for the [Coconut Programming Language](http://evhub.github.io/coconut/)! Coconut is a variant of [Python](https://www.python.org/) built for **simple, elegant, Pythonic functional programming**. But those are just words; what they mean in practice is that _all valid Python 3 is valid Coconut_ but Coconut builds on top of Python a suite of _simple, elegant utilities for functional programming_. -Why use Coconut? Coconut is built to be fundamentally _useful_. Coconut enhances the repertoire of Python programmers to include the tools of modern functional programming, in such a way that those tools are _easy_ to use and immensely _powerful_; that is, _Coconut does to functional programming what Python did to imperative programming_. And Coconut code runs the same on _any Python version_, making the Python 2/3 split a thing of the past. +Why use Coconut? Coconut is built to be useful. Coconut enhances the repertoire of Python programmers to include the tools of modern functional programming, in such a way that those tools are _easy_ to use and immensely _powerful;_ that is, Coconut does to functional programming what Python did to imperative programming. And Coconut code runs the same on _any Python version_, making the Python 2/3 split a thing of the past. Specifically, Coconut adds to Python _built-in, syntactical support_ for: - pattern-matching From 296aa66678939f84a269755313072a4225b6215e Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Wed, 3 May 2017 12:32:20 -0700 Subject: [PATCH 164/176] Fix broken test --- tests/src/cocotest/agnostic/suite.coco | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/cocotest/agnostic/suite.coco b/tests/src/cocotest/agnostic/suite.coco index 91819c1ed..3988a9d37 100644 --- a/tests/src/cocotest/agnostic/suite.coco +++ b/tests/src/cocotest/agnostic/suite.coco @@ -230,7 +230,7 @@ def suite_test(): assert pattern_abs(-4) == 4 == pattern_abs_(-4) assert vector(1, 2) |> (==)$(vector(1, 2)) assert vector(1, 2) |> .__eq__(other=vector(1, 2)) - assert fib()$[1:4] == (1, 2, 3) + assert fib()$[1:4] |> tuple == (1, 2, 3) assert fib() |> takewhile$((i) -> i < 4000000 ) |> filter$((i) -> i % 2 == 0 ) |> sum == 4613732 assert loop([1,2])$[:4] |> list == [1, 2] * 2 assert (def -> mod)()(5, 3) == 2 From f6d5fd491bea2887dc8a052d9082251af39181bf Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Wed, 3 May 2017 15:23:12 -0700 Subject: [PATCH 165/176] Improve stdin tty checking --- coconut/command/command.py | 8 +++++--- coconut/command/util.py | 11 +++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/coconut/command/command.py b/coconut/command/command.py index 859e0902c..38ae45990 100644 --- a/coconut/command/command.py +++ b/coconut/command/command.py @@ -59,6 +59,7 @@ is_special_dir, launch_documentation, launch_tutorial, + stdin_readable, ) from coconut.compiler.util import should_indent from coconut.compiler.header import gethash @@ -216,13 +217,14 @@ def use_args(self, args, interact=True): if args.code is not None: self.execute(self.comp.parse_block(args.code)) - stdin = not sys.stdin.isatty() # check if input was piped in - if stdin: + read_stdin = False + if stdin_readable(): self.execute(self.comp.parse_block(sys.stdin.read())) + read_stdin = True if args.jupyter is not None: self.start_jupyter(args.jupyter) if args.interact or (interact and not ( - stdin + read_stdin or args.source or args.code or args.tutorial diff --git a/coconut/command/util.py b/coconut/command/util.py index 37af0c269..e29e69195 100644 --- a/coconut/command/util.py +++ b/coconut/command/util.py @@ -27,6 +27,7 @@ import webbrowser from copy import copy from contextlib import contextmanager +from select import select if PY26: import imp else: @@ -257,6 +258,16 @@ def set_mypy_path(mypy_path): os.environ[mypy_path_env_var] = mypy_path + os.pathsep + original +def stdin_readable(): + """Determines whether stdin has any data to read.""" + if sys.stdin.isatty(): + return False + try: + return bool(select([sys.stdin], [], [], 0)[0]) + except OSError: + return True + + #----------------------------------------------------------------------------------------------------------------------- # CLASSES: #----------------------------------------------------------------------------------------------------------------------- From cf361857e69999a36d9102323ce76c0ad87410e0 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Wed, 3 May 2017 22:01:07 -0700 Subject: [PATCH 166/176] Improve handling of stdout, stdin Resolves #215. --- coconut/command/command.py | 1 + coconut/command/util.py | 5 ++++- coconut/root.py | 5 +++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/coconut/command/command.py b/coconut/command/command.py index 38ae45990..984f54744 100644 --- a/coconut/command/command.py +++ b/coconut/command/command.py @@ -219,6 +219,7 @@ def use_args(self, args, interact=True): self.execute(self.comp.parse_block(args.code)) read_stdin = False if stdin_readable(): + logger.log("Reading piped input from stdin...") self.execute(self.comp.parse_block(sys.stdin.read())) read_stdin = True if args.jupyter is not None: diff --git a/coconut/command/util.py b/coconut/command/util.py index e29e69195..0a9844e55 100644 --- a/coconut/command/util.py +++ b/coconut/command/util.py @@ -265,7 +265,10 @@ def stdin_readable(): try: return bool(select([sys.stdin], [], [], 0)[0]) except OSError: - return True + pass + if not sys.stdout.isatty(): + return False + return True #----------------------------------------------------------------------------------------------------------------------- diff --git a/coconut/root.py b/coconut/root.py index 5f6efd310..c49356d3e 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -113,8 +113,9 @@ def __subclasscheck__(cls, subcls): from functools import wraps as _coconut_wraps @_coconut_wraps(_coconut_print) def print(*args, **kwargs): - if _coconut.hasattr(_coconut_sys.stdout, "encoding") and _coconut_sys.stdout.encoding is not None: - return _coconut_print(*(_coconut_unicode(x).encode(_coconut_sys.stdout.encoding) for x in args), **kwargs) + file = kwargs.get("file", _coconut_sys.stdout) + if _coconut.hasattr(file, "encoding") and file.encoding is not None: + return _coconut_print(*(_coconut_unicode(x).encode(file.encoding) for x in args), **kwargs) else: return _coconut_print(*(_coconut_unicode(x).encode() for x in args), **kwargs) @_coconut_wraps(_coconut_raw_input) From 3fc2fb9e1d074011c6f9ffded30bfb19a27c2c8f Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Wed, 3 May 2017 22:10:15 -0700 Subject: [PATCH 167/176] Add stdout flushing --- coconut/command/util.py | 1 + 1 file changed, 1 insertion(+) diff --git a/coconut/command/util.py b/coconut/command/util.py index 0a9844e55..6966c73bd 100644 --- a/coconut/command/util.py +++ b/coconut/command/util.py @@ -309,6 +309,7 @@ def set_style(self, style): @handling_prompt_toolkit_errors def input(self, more=False): """Prompts for code input.""" + sys.stdout.flush() if more: msg = more_prompt else: From 515a07206328b4a68f070c08dfa34bd1191c9520 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Wed, 3 May 2017 22:43:38 -0700 Subject: [PATCH 168/176] Removes unnecessary version upper bounds Resolves #238. --- coconut/constants.py | 10 ++++++++- coconut/requirements.py | 45 +++++++++++++++++++++++------------------ 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/coconut/constants.py b/coconut/constants.py index d35680fea..bdbac861d 100644 --- a/coconut/constants.py +++ b/coconut/constants.py @@ -101,7 +101,7 @@ def get_target_info(target): ], } -req_vers = { +min_versions = { "pyparsing": (2, 2, 0), "pre-commit": (0, 13), "sphinx": (1, 5), @@ -122,6 +122,14 @@ def get_target_info(target): "requests": (2, 13), } +version_strictly = [ + "pyparsing", + "sphinx", + "sphinx_bootstrap_theme", + "ipython", + "ipykernel", +] + classifiers = [ "Development Status :: 5 - Production/Stable", "License :: OSI Approved :: Apache Software License", diff --git a/coconut/requirements.py b/coconut/requirements.py index 9a38cac51..152f0a2bd 100644 --- a/coconut/requirements.py +++ b/coconut/requirements.py @@ -22,25 +22,42 @@ import setuptools -from coconut.constants import all_reqs, req_vers +from coconut.constants import ( + all_reqs, + min_versions, + version_strictly, +) #----------------------------------------------------------------------------------------------------------------------- # UTILITIES: #----------------------------------------------------------------------------------------------------------------------- -def req_str(req_ver): +def ver_tuple_to_str(req_ver): """Converts a requirement version tuple into a version string.""" return ".".join(str(x) for x in req_ver) +def ver_str_to_tuple(ver_str): + """Convert a version string into a version tuple.""" + out = [] + for x in ver_str.split("."): + try: + x = int(x) + except ValueError: + pass + out.append(x) + return tuple(out) + + def get_reqs(which="main"): - """Gets requirements from all_reqs with req_vers.""" + """Gets requirements from all_reqs with versions.""" reqs = [] for req in all_reqs[which]: - cur_ver = req_str(req_vers[req]) - next_ver = req_str(req_vers[req][:-1]) + "." + str(req_vers[req][-1] + 1) - reqs.append(req + ">=" + cur_ver + ",<" + next_ver) + req_str = req + ">=" + ver_tuple_to_str(min_versions[req]) + if req in version_strictly: + req_str += ",<" + ver_tuple_to_str(min_versions[req][:-1]) + "." + str(min_versions[req][-1] + 1) + reqs.append(req_str) return reqs @@ -126,18 +143,6 @@ def all_versions(req): return tuple(requests.get(url).json()["releases"].keys()) -def ver_tuple(ver_str): - """Convert a version string into a version tuple.""" - out = [] - for x in ver_str.split("."): - try: - x = int(x) - except ValueError: - pass - out.append(x) - return tuple(out) - - def newer(new_ver, old_ver): """Determines if the first version tuple is newer than the second.""" if old_ver == new_ver or old_ver + (0,) == new_ver: @@ -160,12 +165,12 @@ def print_new_versions(strict=False): new_versions = [] same_versions = [] for ver_str in all_versions(req): - comp = newer(ver_tuple(ver_str), req_vers[req]) + comp = newer(ver_str_to_tuple(ver_str), min_versions[req]) if comp is True: new_versions.append(ver_str) elif not strict and comp is None: same_versions.append(ver_str) - update_str = req + ": " + req_str(req_vers[req]) + " -> " + ", ".join( + update_str = req + ": " + ver_tuple_to_str(min_versions[req]) + " -> " + ", ".join( new_versions + ["(" + v + ")" for v in same_versions] ) if new_versions: From 0d1b19a33280dc48cb317ce63a3343a95b520ea0 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Wed, 3 May 2017 22:46:09 -0700 Subject: [PATCH 169/176] Bump develop version --- coconut/root.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coconut/root.py b/coconut/root.py index c49356d3e..6476cc024 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -26,7 +26,7 @@ VERSION = "1.2.2" VERSION_NAME = "Colonel" # False for release, int >= 1 for develop -DEVELOP = 17 +DEVELOP = 18 #----------------------------------------------------------------------------------------------------------------------- # CONSTANTS: From 79cc626e24793cc26d16f9d694a184debe66b9ad Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Thu, 4 May 2017 00:30:43 -0700 Subject: [PATCH 170/176] Improve error messages, clean up code --- coconut/command/command.py | 24 ++++++------- coconut/compiler/compiler.py | 69 +++++++++++++++++++----------------- coconut/compiler/grammar.py | 2 +- coconut/constants.py | 3 +- 4 files changed, 49 insertions(+), 49 deletions(-) diff --git a/coconut/command/command.py b/coconut/command/command.py index 984f54744..ac5e04178 100644 --- a/coconut/command/command.py +++ b/coconut/command/command.py @@ -89,12 +89,11 @@ def start(self, run=False): """Processes command-line arguments.""" if run: args, argv = ["--run", "--quiet", "--target", "sys"], [] + # for coconut-run, all args beyond the source file should stay in sys.argv for i in range(1, len(sys.argv)): arg = sys.argv[i] - if arg.startswith("-"): - args.append(arg) # coconut option - else: - args.append(arg) # source file + args.append(arg) + if not arg.startswith("-"): # is source file argv = sys.argv[i:] break sys.argv = argv @@ -217,15 +216,15 @@ def use_args(self, args, interact=True): if args.code is not None: self.execute(self.comp.parse_block(args.code)) - read_stdin = False + got_stdin = False if stdin_readable(): logger.log("Reading piped input from stdin...") self.execute(self.comp.parse_block(sys.stdin.read())) - read_stdin = True + got_stdin = True if args.jupyter is not None: self.start_jupyter(args.jupyter) if args.interact or (interact and not ( - read_stdin + got_stdin or args.source or args.code or args.tutorial @@ -271,10 +270,7 @@ def compile_path(self, path, write=True, package=None, *args, **kwargs): if package is None: package = False destpath = self.compile_file(path, write, package, *args, **kwargs) - if destpath is None: - return [] - else: - return [destpath] + return [destpath] if destpath is not None else [] elif os.path.isdir(path): if package is None: package = True @@ -332,7 +328,7 @@ def compile(self, codepath, destpath=None, package=False, run=False, force=False if package is True: self.create_package(destdir) - foundhash = None if force else self.hashashof(destpath, code, package) + foundhash = None if force else self.has_hash_of(destpath, code, package) if foundhash: if show_unchanged: logger.show_tabulated("Left unchanged", showpath(destpath), "(pass --force to override).") @@ -394,7 +390,7 @@ def set_jobs(self, jobs): try: jobs = int(jobs) except ValueError: - jobs = -1 + jobs = -1 # will raise error below if jobs < 0: raise CoconutException("--jobs must be an integer >= 0 or 'sys'") else: @@ -423,7 +419,7 @@ def create_package(self, dirpath): with openfile(filepath, "w") as opened: writefile(opened, self.comp.getheader("__coconut__")) - def hashashof(self, destpath, code, package): + def has_hash_of(self, destpath, code, package): """Determines if a file has the hash of the code.""" if destpath is not None and os.path.isfile(destpath): with openfile(destpath, "r") as opened: diff --git a/coconut/compiler/compiler.py b/coconut/compiler/compiler.py index af5802f7f..3dda2e4f9 100644 --- a/coconut/compiler/compiler.py +++ b/coconut/compiler/compiler.py @@ -68,7 +68,7 @@ stmt_lambda_var, tre_mock_var, tre_store_var, - new_to_old_stdlib, + py3_to_py2_stdlib, default_recursion_limit, checksum, reserved_prefix, @@ -685,37 +685,32 @@ def leading(self, inputstring): def ind_proc(self, inputstring, **kwargs): """Processes indentation.""" lines = inputstring.splitlines() - new = [] - levels = [] - count = 0 - current = None - skips = self.skips.copy() - - for ln in range(len(lines)): - line = lines[ln] - ln += 1 + new = [] # new lines + opens = [] # (line, col, adjusted ln) at which open parens were seen, newest first + current = None # indentation level of previous line + levels = [] # indentation levels of all previous blocks, newest at end + skips = self.skips.copy() # todo + + for ln in range(1, len(lines) + 1): # ln is 1-indexed + line = lines[ln - 1] # lines is 0-indexed line_rstrip = line.rstrip() if line != line_rstrip: if self.strict: raise self.make_err(CoconutStyleError, "found trailing whitespace", line, len(line), self.adjust(ln)) + line = line_rstrip + last = rem_comment(new[-1]) if new else None + + if not line or line.lstrip().startswith("#"): # blank line or comment + if opens: # inside parens + skips = addskip(skips, self.adjust(ln)) else: - line = line_rstrip - if new: - last = rem_comment(new[-1]) - else: - last = None - if not line or line.lstrip().startswith("#"): - if count >= 0: new.append(line) - else: - skips = addskip(skips, self.adjust(ln)) - elif last is not None and last.endswith("\\"): + elif last is not None and last.endswith("\\"): # backslash line continuation if self.strict: raise self.make_err(CoconutStyleError, "found backslash continuation", last, len(last), self.adjust(ln - 1)) - else: - skips = addskip(skips, self.adjust(ln)) - new[-1] = last[:-1] + " " + line - elif count < 0: + skips = addskip(skips, self.adjust(ln)) + new[-1] = last[:-1] + " " + line + elif opens: # inside parens skips = addskip(skips, self.adjust(ln)) new[-1] = last + " " + line else: @@ -737,17 +732,23 @@ def ind_proc(self, inputstring, **kwargs): elif current != check: raise self.make_err(CoconutSyntaxError, "illegal dedent to unused indentation level", line, 0, self.adjust(ln)) new.append(line) - count += paren_change(line) - if count > 0: - raise self.make_err(CoconutSyntaxError, "unmatched close parentheses", new[-1], len(new[-1]), self.adjust(len(new))) + + count = paren_change(line) # (num close parens) - (num open parens) + if count > len(opens): + raise self.make_err(CoconutSyntaxError, "unmatched close parenthesis", new[-1], 0, self.adjust(len(new))) + elif count > 0: + opens = opens[-count:] # pop an open for each extra close + else: + opens += [(new[-1], self.adjust(len(new)))] * (-count) self.skips = skips if new: last = rem_comment(new[-1]) if last.endswith("\\"): raise self.make_err(CoconutSyntaxError, "illegal final backslash continuation", new[-1], len(new[-1]), self.adjust(len(new))) - if count != 0: - raise self.make_err(CoconutSyntaxError, "unclosed open parentheses", new[-1], len(new[-1]), self.adjust(len(new))) + if opens: + line, adj_ln = opens[0] + raise self.make_err(CoconutSyntaxError, "unclosed open parenthesis", line, 0, adj_ln) new.append(closeindent * len(levels)) return "\n".join(new) @@ -901,7 +902,7 @@ def str_repl(self, inputstring, **kwargs): if out and not out[-1].endswith("\n"): out[-1] = out[-1].rstrip(" ") if not self.minify: - out[-1] += " " # enforce two spaces before comment + out[-1] += " " # put two spaces before comment out.append("#" + ref) comment = None else: @@ -1163,6 +1164,7 @@ def import_handle(self, original, loc, tokens): return "" else: raise CoconutInternalException("invalid import tokens", tokens) + importmap = [] # [((imp | old_imp, imp, version_check), impas), ...] for imps in imports: if len(imps) == 1: @@ -1176,8 +1178,8 @@ def import_handle(self, original, loc, tokens): for i in reversed(range(1, len(path) + 1)): base, exts = ".".join(path[:i]), path[i:] clean_base = base.replace("/", "") - if clean_base in new_to_old_stdlib: - old_imp, version_check = new_to_old_stdlib[clean_base] + if clean_base in py3_to_py2_stdlib: + old_imp, version_check = py3_to_py2_stdlib[clean_base] if exts: if "/" in base: old_imp += "./" @@ -1194,6 +1196,7 @@ def import_handle(self, original, loc, tokens): else: paths = (imp,) importmap.append((paths, impas)) + stmts = [] for paths, impas in importmap: if len(paths) == 1: @@ -1491,7 +1494,7 @@ def decoratable_normal_funcdef_stmt_handle(self, tokens): return out def unsafe_typedef_handle(self, tokens): - """Handles unsafe type annotations.""" + """Handles type annotations without a comma after them.""" return self.typedef_handle(tokens.asList() + [","]) def typedef_handle(self, tokens): diff --git a/coconut/compiler/grammar.py b/coconut/compiler/grammar.py index e1b22481c..0af790751 100644 --- a/coconut/compiler/grammar.py +++ b/coconut/compiler/grammar.py @@ -768,8 +768,8 @@ class Grammar(object): default = condense(equals + test) arg_comma = comma | fixto(FollowedBy(rparen), "") typedef_ref = name + colon.suppress() + test + arg_comma - typedef_default_ref = name + colon.suppress() + test + Optional(default) + arg_comma unsafe_typedef_default_ref = name + colon.suppress() + test + Optional(default) + typedef_default_ref = unsafe_typedef_default_ref + arg_comma tfpdef = typedef | condense(name + arg_comma) tfpdef_default = typedef_default | condense(name + Optional(default) + arg_comma) diff --git a/coconut/constants.py b/coconut/constants.py index bdbac861d..2e781507d 100644 --- a/coconut/constants.py +++ b/coconut/constants.py @@ -343,7 +343,8 @@ def get_target_info(target): "await", ) -new_to_old_stdlib = { # new_name: (old_name, before_version_info) +py3_to_py2_stdlib = { + # new_name: (old_name, before_version_info) "builtins": ("__builtin__", (3,)), "configparser": ("ConfigParser", (3,)), "copyreg": ("copy_reg", (3,)), From ec5bd1f8904a72fe8f49b97ad38e1a7011cb2371 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Thu, 4 May 2017 00:48:37 -0700 Subject: [PATCH 171/176] Fix paren processing --- coconut/compiler/compiler.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/coconut/compiler/compiler.py b/coconut/compiler/compiler.py index 3dda2e4f9..24271dde3 100644 --- a/coconut/compiler/compiler.py +++ b/coconut/compiler/compiler.py @@ -736,9 +736,10 @@ def ind_proc(self, inputstring, **kwargs): count = paren_change(line) # (num close parens) - (num open parens) if count > len(opens): raise self.make_err(CoconutSyntaxError, "unmatched close parenthesis", new[-1], 0, self.adjust(len(new))) - elif count > 0: - opens = opens[-count:] # pop an open for each extra close - else: + elif count > 0: # closes > opens + for i in range(count): + opens.pop() + elif count < 0: # opens > closes opens += [(new[-1], self.adjust(len(new)))] * (-count) self.skips = skips From ff94b6e1d49c7849c8c1dc8683a351a11aeedb15 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Tue, 9 May 2017 18:20:55 -0700 Subject: [PATCH 172/176] Improve mypy, error handling --- .gitignore | 3 +++ coconut/command/command.py | 20 ++++++-------------- coconut/command/util.py | 8 ++++---- coconut/compiler/compiler.py | 5 +++-- coconut/constants.py | 4 ++-- coconut/stubs/__coconut__.pyi | 3 +++ coconut/terminal.py | 8 ++++++++ 7 files changed, 29 insertions(+), 22 deletions(-) diff --git a/.gitignore b/.gitignore index b4280fa42..bca714a2a 100644 --- a/.gitignore +++ b/.gitignore @@ -70,6 +70,9 @@ target/ # Sublime *.sublime-* +# MyPy +.mypy_cache/ + # Coconut tests/dest/ docs/ diff --git a/coconut/command/command.py b/coconut/command/command.py index ac5e04178..222bd5df1 100644 --- a/coconut/command/command.py +++ b/coconut/command/command.py @@ -141,6 +141,8 @@ def use_args(self, args, interact=True): logger.quiet, logger.verbose = args.quiet, args.verbose if DEVELOP: logger.tracing = args.trace + logger.log("Command args:", args) + if args.recursion_limit is not None: self.set_recursion_limit(args.recursion_limit) if args.jobs is not None: @@ -530,29 +532,19 @@ def set_mypy_args(self, mypy_args=None): self.mypy_args = list(mypy_args) for arg in self.mypy_args: - if arg == "--fast-parser": - logger.warn("unnecessary --mypy argument", arg, extra="passed automatically if available") - elif arg == "--py2" or arg == "-2": + if arg == "--py2" or arg == "-2": logger.warn("unnecessary --mypy argument", arg, extra="passed automatically when needed") elif arg == "--python-version": logger.warn("unnecessary --mypy argument", arg, extra="current --target passed as version automatically") - if "--fast-parser" not in self.mypy_args: - try: - import typed_ast # NOQA - except ImportError: - if self.comp.target != "3": - logger.warn("missing typed_ast; MyPy may not properly analyze type annotations", - extra="run 'pip install typed_ast' or pass '--target 3' to fix") - else: - self.mypy_args.append("--fast-parser") - if not ("--py2" in self.mypy_args or "-2" in self.mypy_args) and not self.comp.target.startswith("3"): self.mypy_args.append("--py2") if "--python-version" not in self.mypy_args: self.mypy_args += ["--python-version", ".".join(str(v) for v in self.comp.target_info_len2)] + logger.log("MyPy args:", self.mypy_args) + def run_mypy(self, paths=[], code=None): """Run MyPy with arguments.""" set_mypy_path(stub_dir) @@ -606,7 +598,7 @@ def start_jupyter(self, args): elif args[0] == "notebook": run_args = [jupyter, "notebook"] + args[1:] else: - raise CoconutException('first argument after --jupyter must be either "console" or "notebook"') + raise CoconutException("first argument after --jupyter must be either 'console' or 'notebook'") self.register_error(run_cmd(run_args, raise_errs=False), errmsg="Jupyter error") def watch(self, source, write=True, package=None, run=False, force=False): diff --git a/coconut/command/util.py b/coconut/command/util.py index 6966c73bd..caf411044 100644 --- a/coconut/command/util.py +++ b/coconut/command/util.py @@ -155,9 +155,9 @@ def handles_prompt_toolkit_errors_func(self, *args, **kwargs): if self.style is not None: try: return func(self, *args, **kwargs) - except (KeyboardInterrupt, EOFError): - raise - except (Exception, AssertionError): + except EOFError: + raise # issubclass(EOFError, Exception), so we have to do this + except Exception: logger.print_exc() logger.show("Syntax highlighting failed; switching to --style none.") self.style = None @@ -204,7 +204,7 @@ def splitname(path): def run_file(path): - """Runs a module from a path.""" + """Runs a module from a path and return its variables.""" if PY26: dirpath, name = splitname(path) found = imp.find_module(name, [dirpath]) diff --git a/coconut/compiler/compiler.py b/coconut/compiler/compiler.py index 24271dde3..2ecba1dcb 100644 --- a/coconut/compiler/compiler.py +++ b/coconut/compiler/compiler.py @@ -255,8 +255,9 @@ def setup(self, target=None, strict=False, minify=False, line_numbers=False, kee if target in pseudo_targets: target = pseudo_targets[target] if target not in targets: - raise CoconutException('unsupported target Python version "' + target - + '" (supported targets are "' + '", "'.join(specific_targets) + '", or leave blank for universal)') + raise CoconutException("unsupported target Python version " + ascii(target), + extra="supported targets are " + ', '.join(ascii(t) for t in specific_targets) + ", or leave blank for universal") + logger.log_vars("Compiler args:", locals()) self.target, self.strict, self.minify, self.line_numbers, self.keep_lines, self.no_tco = ( target, strict, minify, line_numbers, keep_lines, no_tco) diff --git a/coconut/constants.py b/coconut/constants.py index 2e781507d..300408727 100644 --- a/coconut/constants.py +++ b/coconut/constants.py @@ -113,13 +113,13 @@ def get_target_info(target): "jupyter-console": (5, 1), "ipython": (5, 3), "ipykernel": (4, 6), - "mypy": (0, 501), + "mypy": (0, 511), "prompt_toolkit": (1, 0), "futures": (3, 1), "argparse": (1, 4), "pytest": (3, 0), "watchdog": (0, 8), - "requests": (2, 13), + "requests": (2,), } version_strictly = [ diff --git a/coconut/stubs/__coconut__.pyi b/coconut/stubs/__coconut__.pyi index e072a096a..e16672a51 100644 --- a/coconut/stubs/__coconut__.pyi +++ b/coconut/stubs/__coconut__.pyi @@ -40,6 +40,9 @@ if sys.version_info < (3,): def count(self, elem: int) -> int: ... def index(self, elem: int) -> int: ... +else: + import builtins as _b + py_chr, py_filter, py_hex, py_input, py_int, py_map, py_object, py_oct, py_open, py_print, py_range, py_str, py_zip, py_filter, py_reversed, py_enumerate = _b.chr, _b.filter, _b.hex, _b.input, _b.int, _b.map, _b.object, _b.oct, _b.open, _b.print, _b.range, _b.str, _b.zip, _b.filter, _b.reversed, _b.enumerate diff --git a/coconut/terminal.py b/coconut/terminal.py index 3a7d88d93..1212a8a05 100644 --- a/coconut/terminal.py +++ b/coconut/terminal.py @@ -125,6 +125,14 @@ def log_show(self, *messages): if self.verbose: self.display(messages, main_sig, debug=True) + def log_vars(self, message, variables, rem_vars=("self",)): + """Logs variables with given message.""" + if self.verbose: + new_vars = dict(variables) + for v in rem_vars: + del new_vars[v] + printerr(message, new_vars) + def get_error(self): """Properly formats the current error.""" exc_info = sys.exc_info() From beee0db9defca48162d6c26e18a4a008dd133290 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Thu, 11 May 2017 16:36:51 -0700 Subject: [PATCH 173/176] Improve interpreter --- coconut/command/command.py | 4 ++-- coconut/command/util.py | 44 +++++++++++++----------------------- coconut/compiler/matching.py | 1 + coconut/compiler/util.py | 8 +++---- coconut/constants.py | 5 +++- coconut/terminal.py | 1 + 6 files changed, 28 insertions(+), 35 deletions(-) diff --git a/coconut/command/command.py b/coconut/command/command.py index 222bd5df1..1fe7b1895 100644 --- a/coconut/command/command.py +++ b/coconut/command/command.py @@ -41,7 +41,7 @@ icoconut_kernel_dirs, minimum_recursion_limit, stub_dir, - exit_char, + exit_chars, ) from coconut.command.util import ( openfile, @@ -442,7 +442,7 @@ def get_input(self, more=False): print() self.exit_runner() else: - if received.startswith(exit_char): + if received.startswith(exit_chars): self.exit_runner() received = None return received diff --git a/coconut/command/util.py b/coconut/command/util.py index caf411044..3a8a1cd5c 100644 --- a/coconut/command/util.py +++ b/coconut/command/util.py @@ -22,7 +22,6 @@ import sys import os import traceback -import functools import subprocess import webbrowser from copy import copy @@ -121,7 +120,7 @@ def rem_encoding(code): new_lines = [] for i in range(min(2, len(old_lines))): line = old_lines[i] - if not (line.startswith("#") and "coding" in line): + if not (line.lstrip().startswith("#") and "coding" in line): new_lines.append(line) new_lines += old_lines[2:] return "\n".join(new_lines) @@ -148,23 +147,6 @@ def interpret(code, in_vars): exec_func(code, in_vars) -def handling_prompt_toolkit_errors(func): - """Handles prompt_toolkit and pygments errors.""" - @functools.wraps(func) - def handles_prompt_toolkit_errors_func(self, *args, **kwargs): - if self.style is not None: - try: - return func(self, *args, **kwargs) - except EOFError: - raise # issubclass(EOFError, Exception), so we have to do this - except Exception: - logger.print_exc() - logger.show("Syntax highlighting failed; switching to --style none.") - self.style = None - return func(self, *args, **kwargs) - return handles_prompt_toolkit_errors_func - - @contextmanager def handling_broken_process_pool(): """Handles BrokenProcessPool error.""" @@ -183,7 +165,8 @@ def kill_children(): try: import psutil except ImportError: - logger.warn("missing psutil; --jobs may not properly terminate", extra="run 'pip install coconut[jobs]' to fix") + logger.warn("missing psutil; --jobs may not properly terminate", + extra="run 'pip install coconut[jobs]' to fix") else: master = psutil.Process() children = master.children(recursive=True) @@ -204,7 +187,7 @@ def splitname(path): def run_file(path): - """Runs a module from a path and return its variables.""" + """Run a module from a path and return its variables.""" if PY26: dirpath, name = splitname(path) found = imp.find_module(name, [dirpath]) @@ -306,7 +289,6 @@ def set_style(self, style): else: raise CoconutException("unrecognized pygments style", style, extra="use '--style list' to show all valid styles") - @handling_prompt_toolkit_errors def input(self, more=False): """Prompts for code input.""" sys.stdout.flush() @@ -314,12 +296,18 @@ def input(self, more=False): msg = more_prompt else: msg = main_prompt - if self.style is None: - return input(msg) - elif prompt_toolkit is None: - raise CoconutInternalException("cannot highlight style without prompt_toolkit", self.style) - else: - return prompt_toolkit.prompt(msg, **self.prompt_kwargs()) + if self.style is not None: + if prompt_toolkit is None: + raise CoconutInternalException("cannot highlight style without prompt_toolkit", self.style) + try: + return prompt_toolkit.prompt(msg, **self.prompt_kwargs()) + except EOFError: + raise # issubclass(EOFError, Exception), so we have to do this + except Exception: + logger.print_exc() + logger.show("Syntax highlighting failed; switching to --style none.") + self.style = None + return input(msg) def prompt_kwargs(self): """Gets prompt_toolkit.prompt keyword args.""" diff --git a/coconut/compiler/matching.py b/coconut/compiler/matching.py index 59266698a..12958a2de 100644 --- a/coconut/compiler/matching.py +++ b/coconut/compiler/matching.py @@ -58,6 +58,7 @@ def get_match_names(match): names += get_match_names(match) return names + #----------------------------------------------------------------------------------------------------------------------- # MATCHER: #----------------------------------------------------------------------------------------------------------------------- diff --git a/coconut/compiler/util.py b/coconut/compiler/util.py index 09ecb429f..d91b24ca5 100644 --- a/coconut/compiler/util.py +++ b/coconut/compiler/util.py @@ -83,18 +83,18 @@ def count_end(teststr, testchar): def paren_change(inputstring): - """Determines the parenthetical change of level.""" + """Determines the parenthetical change of level (num closes - num opens).""" count = 0 for c in inputstring: - if c in downs: + if c in downs: # open parens/brackets/braces count -= 1 - elif c in ups: + elif c in ups: # close parens/brackets/braces count += 1 return count def ind_change(inputstring): - """Determines the change in indentation level.""" + """Determines the change in indentation level (num opens - num closes).""" return inputstring.count(openindent) - inputstring.count(closeindent) diff --git a/coconut/constants.py b/coconut/constants.py index 300408727..8ccaf1927 100644 --- a/coconut/constants.py +++ b/coconut/constants.py @@ -424,7 +424,10 @@ def get_target_info(target): stub_dir = os.path.join(base_dir, "stubs") -exit_char = "\x1a" +exit_chars = ( + "\x04", # Ctrl-D + "\x1a", # Ctrl-Z +) #----------------------------------------------------------------------------------------------------------------------- # HIGHLIGHTER CONSTANTS: diff --git a/coconut/terminal.py b/coconut/terminal.py index 1212a8a05..f80bf748d 100644 --- a/coconut/terminal.py +++ b/coconut/terminal.py @@ -70,6 +70,7 @@ def complain(error): else: logger.warn_err(error) + #----------------------------------------------------------------------------------------------------------------------- # logger: #----------------------------------------------------------------------------------------------------------------------- From cd75c6e07940da0106d71a4ad6286832b2d7782e Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Fri, 12 May 2017 13:17:42 -0700 Subject: [PATCH 174/176] Fix merge issues --- Makefile | 1 - conf.py | 10 ---------- 2 files changed, 11 deletions(-) diff --git a/Makefile b/Makefile index 4978f964b..ef55a403f 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,6 @@ sphinx: clean .PHONY: docs docs: sphinx pushd ./docs; zip -r ./docs.zip ./*; popd - rm -rf index.rst .PHONY: clean clean: diff --git a/conf.py b/conf.py index cbf581f9a..8ca6177dd 100644 --- a/conf.py +++ b/conf.py @@ -37,16 +37,6 @@ # README: #----------------------------------------------------------------------------------------------------------------------- -with open("README.rst", "r") as readme_file: - readme = readme_file.read() - -with open("index.rst", "w") as index_file: - index_file.write(readme.replace(without_toc, with_toc)) - -#----------------------------------------------------------------------------------------------------------------------- -# README: -#----------------------------------------------------------------------------------------------------------------------- - with open("README.rst", "r") as readme_file: readme = readme_file.read() From c739d76f5c49d768871e98e8a9bf596427a1fed2 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Mon, 15 May 2017 00:48:13 -0700 Subject: [PATCH 175/176] Improve cli warnings and errors --- coconut/command/command.py | 7 +++++-- coconut/constants.py | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/coconut/command/command.py b/coconut/command/command.py index 1fe7b1895..5f11d6c3e 100644 --- a/coconut/command/command.py +++ b/coconut/command/command.py @@ -42,6 +42,7 @@ minimum_recursion_limit, stub_dir, exit_chars, + coconut_run_args, ) from coconut.command.util import ( openfile, @@ -88,7 +89,7 @@ def __init__(self): def start(self, run=False): """Processes command-line arguments.""" if run: - args, argv = ["--run", "--quiet", "--target", "sys"], [] + args, argv = coconut_run_args, [] # for coconut-run, all args beyond the source file should stay in sys.argv for i in range(1, len(sys.argv)): arg = sys.argv[i] @@ -166,6 +167,8 @@ def use_args(self, args, interact=True): ) if args.mypy is not None: + if args.no_tco: + logger.warn("extraneous --no-tco argument passed; --mypy implies --no-tco") self.set_mypy_args(args.mypy) if args.source is not None: @@ -191,7 +194,7 @@ def use_args(self, args, interact=True): else: dest = True # auto-generate dest elif args.no_write: - raise CoconutException("destination path cannot be given when --nowrite is enabled") + raise CoconutException("destination path cannot be given when --no-write is enabled") elif os.path.isfile(args.dest): raise CoconutException("destination path must point to directory not file") else: diff --git a/coconut/constants.py b/coconut/constants.py index 8ccaf1927..5090629b1 100644 --- a/coconut/constants.py +++ b/coconut/constants.py @@ -429,6 +429,8 @@ def get_target_info(target): "\x1a", # Ctrl-Z ) +coconut_run_args = ["--run", "--quiet", "--target", "sys"] + #----------------------------------------------------------------------------------------------------------------------- # HIGHLIGHTER CONSTANTS: #----------------------------------------------------------------------------------------------------------------------- From cfbdf1e8cd2475ec4b1e1e81f3f8f444e11f1a57 Mon Sep 17 00:00:00 2001 From: Evan Hubinger Date: Mon, 15 May 2017 00:52:19 -0700 Subject: [PATCH 176/176] Set version to 1.2.3 --- coconut/root.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/coconut/root.py b/coconut/root.py index 6476cc024..5599f5810 100644 --- a/coconut/root.py +++ b/coconut/root.py @@ -23,10 +23,10 @@ # VERSION: #----------------------------------------------------------------------------------------------------------------------- -VERSION = "1.2.2" +VERSION = "1.2.3" VERSION_NAME = "Colonel" # False for release, int >= 1 for develop -DEVELOP = 18 +DEVELOP = False #----------------------------------------------------------------------------------------------------------------------- # CONSTANTS: