Skip to content

Commit

Permalink
Merge pull request #111 from evhub/develop
Browse files Browse the repository at this point in the history
Release v1.1.0 [Brontosaurus]
  • Loading branch information
evhub authored Jun 24, 2016
2 parents 36f9240 + a25dc15 commit 7c77590
Show file tree
Hide file tree
Showing 15 changed files with 621 additions and 363 deletions.
432 changes: 224 additions & 208 deletions .travis.yml

Large diffs are not rendered by default.

165 changes: 115 additions & 50 deletions DOCS.md

Large diffs are not rendered by default.

133 changes: 105 additions & 28 deletions HELP.md

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
global-include *.txt
global-include *.rst
global-include *.md
global-include *.json
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Coconut

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_, where it has been downloaded `over 30,000 times <http://pypi-ranking.info/module/coconut>`_. Installing Coconut is as easy as opening a command prompt and entering::
Coconut is developed on GitHub_ and hosted on PyPI_. Installing Coconut is as easy as opening a command prompt and entering::

python -m pip install coconut

Expand Down
89 changes: 67 additions & 22 deletions coconut/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
# CONSTANTS:
#-----------------------------------------------------------------------------------------------------------------------

code_ext = ".coc"
code_exts = [".coco", ".coc", ".coconut"] # in order of preference
comp_ext = ".py"

main_sig = "Coconut: "
Expand Down Expand Up @@ -85,6 +85,13 @@
tutorial_url = "http://coconut.readthedocs.org/en/" + version_tag + "/HELP.html"
documentation_url = "http://coconut.readthedocs.org/en/" + version_tag + "/DOCS.html"

icoconut_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "icoconut")
icoconut_kernel_dirs = [
os.path.join(icoconut_dir, "coconut"),
os.path.join(icoconut_dir, "coconut2"),
os.path.join(icoconut_dir, "coconut3")
]

#-----------------------------------------------------------------------------------------------------------------------
# UTILITIES:
#-----------------------------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -123,6 +130,14 @@ def rem_encoding(code):
new_lines += old_lines[2:]
return "\n".join(new_lines)

def try_eval(code, in_vars):
"""Try to evaluate the given code, otherwise execute it."""
try:
return eval(code, in_vars)
except SyntaxError:
pass # exit the exception context before executing code
exec(code, in_vars)

class executor(object):
"""Compiled Python executor."""
def __init__(self, proc=None, exit=None, path=None):
Expand Down Expand Up @@ -286,6 +301,10 @@ def indebug(self):
else:
return self.proc.indebug()

def print_exc(self):
"""Properly prints an exception in the exception context."""
self.console.printerr(get_error(self.indebug()))

def cmd(self, args, interact=True):
"""Parses command-line arguments."""
try:
Expand Down Expand Up @@ -347,7 +366,7 @@ def cmd(self, args, interact=True):
)):
self.start_prompt()
except CoconutException:
self.console.printerr(get_error(self.indebug()))
self.print_exc()
sys.exit(1)

def compile_path(self, path, write=True, package=None, run=False, force=False):
Expand Down Expand Up @@ -380,7 +399,7 @@ def compile_folder(self, directory, write=True, package=True, run=False, force=F
wrote = False
try:
for filename in filenames:
if os.path.splitext(filename)[1] == code_ext:
if os.path.splitext(filename)[1] in code_exts:
self.compile_file(os.path.join(dirpath, filename), writedir, package, run, force)
wrote = True
finally: # if we wrote anything in package mode, we should always add a header file
Expand Down Expand Up @@ -465,7 +484,7 @@ def prompt_with(self, prompt):
print()
self.exit()
except ValueError:
self.console.printerr(get_error(self.indebug()))
self.print_exc()
self.exit()
return None

Expand All @@ -480,7 +499,7 @@ def start_prompt(self):
if code:
compiled = self.handle(code)
if compiled:
self.execute(compiled, False)
self.execute(compiled, error=False, print_expr=True)

def exit(self):
"""Exits the interpreter."""
Expand All @@ -506,18 +525,23 @@ def handle(self, code):
try:
compiled = self.proc.parse_block(code)
except CoconutException:
self.console.printerr(get_error(self.indebug()))
self.print_exc()
return compiled

def execute(self, compiled=None, error=True, path=None, isolate=False):
def execute(self, compiled=None, error=True, path=None, isolate=False, print_expr=False):
"""Executes compiled code."""
self.check_runner(path, isolate)
if compiled is not None:
if self.show:
print(compiled)
if isolate: # isolate means header is included, and thus encoding must be removed
compiled = rem_encoding(compiled)
self.runner.run(compiled, error)
if print_expr:
result = self.runner.run(compiled, error, run_func=try_eval)
if result is not None: # if the input was an expression, we should print it
print(result)
else:
self.runner.run(compiled, error)

def check_runner(self, path=None, isolate=False):
"""Makes sure there is a runner."""
Expand All @@ -543,37 +567,58 @@ def launch_documentation(self):
import webbrowser
webbrowser.open(documentation_url, 2)

def log_cmd(self, args):
"""Logs a console command if indebug."""
if self.indebug():
self.console.printerr("> " + " ".join(args))

def start_jupyter(self, args):
"""Starts Jupyter with the Coconut kernel."""
import subprocess
if args:
install_func = lambda args: subprocess.check_output(args, stderr=subprocess.STDOUT)
if args and not self.indebug():
install_func = lambda args: subprocess.check_output(args, stderr=subprocess.STDOUT) # stdout is returned and ignored
else:
install_func = lambda args: subprocess.check_call(args)
check_args = ["jupyter", "--version"]
self.log_cmd(check_args)
try:
install_func(["jupyter", "--version"])
install_func(check_args)
except subprocess.CalledProcessError:
jupyter = "ipython"
else:
jupyter = "jupyter"
install_args = [jupyter, "kernelspec", "install", os.path.join(os.path.dirname(os.path.abspath(__file__)), "icoconut"), "--replace"]
try:
install_func(install_args)
except subprocess.CalledProcessError:
for icoconut_kernel_dir in icoconut_kernel_dirs:
install_args = [jupyter, "kernelspec", "install", icoconut_kernel_dir, "--replace"]
self.log_cmd(install_args)
try:
install_func(install_args + ["--user"])
install_func(install_args)
except subprocess.CalledProcessError:
errmsg = 'unable to install Jupyter kernel specification file (failed command "'+" ".join(install_args)+'")'
if args:
self.proc.warn(CoconutWarning(errmsg))
else:
raise CoconutException(errmsg)
user_install_args = install_args + ["--user"]
self.log_cmd(user_install_args)
try:
install_func(user_install_args)
except subprocess.CalledProcessError:
errmsg = 'unable to install Jupyter kernel specification file (failed command "'+" ".join(install_args)+'")'
if args:
self.proc.warn(CoconutWarning(errmsg))
else:
raise CoconutException(errmsg)
if args:
if args[0] == "console":
ver = "2" if PY2 else "3"
check_args = ["python"+ver, "-m", "coconut", "--version"]
self.log_cmd(check_args)
try:
install_func(check_args)
except subprocess.CalledProcessError:
kernel_name = "coconut"
else:
kernel_name = "coconut"+ver
self.console.print(version_banner)
run_args = [jupyter, "console", "--kernel", "icoconut"] + args[1:]
run_args = [jupyter, "console", "--kernel", kernel_name] + args[1:]
elif args[0] == "notebook":
run_args = [jupyter, "notebook"] + args[1:]
else:
raise CoconutException('first argument after --jupyter must be either "console" or "notebook"')
self.log_cmd(run_args)
subprocess.call(run_args)
90 changes: 64 additions & 26 deletions coconut/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -616,7 +616,7 @@ def recursive(func):
state = [True, None] # state = [is_top_level, (args, kwargs)]
recurse = object()
@_coconut.functools.wraps(func)
def tailed_func(*args, **kwargs):
def recursive_func(*args, **kwargs):
"""Tail Recursion Wrapper."""
if state[0]:
state[0] = False
Expand All @@ -633,7 +633,29 @@ def tailed_func(*args, **kwargs):
else:
state[1] = args, kwargs
return recurse
return tailed_func
return recursive_func
def addpattern(base_func):
"""Decorator to add a new case to a pattern-matching function, where the new case is checked last."""
def pattern_adder(func):
@_coconut.functools.wraps(func)
def add_pattern_func(*args, **kwargs):
try:
return base_func(*args, **kwargs)
except _coconut_MatchError:
return func(*args, **kwargs)
return add_pattern_func
return pattern_adder
def prepattern(base_func):
"""Decorator to add a new case to a pattern-matching function, where the new case is checked first."""
def pattern_prepender(func):
@_coconut.functools.wraps(func)
def pre_pattern_func(*args, **kwargs):
try:
return func(*args, **kwargs)
except _coconut_MatchError:
return base_func(*args, **kwargs)
return pre_pattern_func
return pattern_prepender
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)
Expand Down Expand Up @@ -703,7 +725,16 @@ def chain_handle(tokens):
else:
return "_coconut.itertools.chain.from_iterable(" + lazy_list_handle(tokens) + ")"

def get_infix_items(tokens, callback):
def infix_error(tokens):
"""Raises inner infix error."""
raise CoconutException("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 CoconutException("invalid infix tokens", tokens)
Expand All @@ -721,18 +752,9 @@ def get_infix_items(tokens, callback):
args.append(arg)
return tokens[1], args

def infix_error(tokens):
"""Raises inner infix error."""
raise CoconutException("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 op_funcdef_handle(tokens):
"""Processes infix defs."""
func, args = get_infix_items(tokens, infix_error)
func, args = get_infix_items(tokens)
return func + "(" + ", ".join(args) + ")"

def pipe_handle(tokens):
Expand Down Expand Up @@ -923,6 +945,11 @@ def decrement(self, forall=False):
for other in self.others:
other.decrement(True)

def add_guard(self, cond):
"""Adds cond as a guard."""
self.increment(True)
self.add_check(cond)

def match_dict(self, original, item):
"""Matches a dictionary."""
if len(original) == 1:
Expand Down Expand Up @@ -1146,8 +1173,7 @@ def match_handle(o, l, tokens, top=True):
matching = matcher()
matching.match(matches, match_to_var)
if cond:
matching.increment(True)
matching.add_check(cond)
matching.add_guard(cond)
out = ""
if top:
out += match_check_var + " = False\n"
Expand Down Expand Up @@ -2142,19 +2168,30 @@ def name_match_funcdef_handle(self, original, loc, tokens):
"""Processes match defs."""
if len(tokens) == 2:
func, matches = tokens
matching = matcher()
matching.match_sequence(("(", matches), match_to_var)
out = "def " + func + " (*" + match_to_var + "):\n" + openindent
out += match_check_var + " = False\n"
out += matching.out()
out += self.pattern_error(original, loc)
return out
cond = None
elif len(tokens) == 3:
func, matches, cond = tokens
else:
raise CoconutException("invalid match function definition tokens", tokens)
matching = matcher()
matching.match_sequence(("(", matches), match_to_var)
if cond is not None:
matching.add_guard(cond)
out = "def " + func + " (*" + match_to_var + "):\n" + openindent
out += match_check_var + " = False\n"
out += matching.out()
out += self.pattern_error(original, loc)
return out

def op_match_funcdef_handle(self, original, loc, tokens):
"""Processes infix match defs."""
return self.name_match_funcdef_handle(original, loc, get_infix_items(tokens, infix_error))
if len(tokens) == 3:
name_tokens = get_infix_items(tokens)
elif len(tokens) == 4:
name_tokens = get_infix_items(tokens[:-1]) + tuple(tokens[-1:])
else:
raise CoconutException("invalid infix match function definition tokens", tokens)
return self.name_match_funcdef_handle(original, loc, name_tokens)

def check_strict(self, name, original, location, tokens):
"""Checks that syntax meets --strict requirements."""
Expand Down Expand Up @@ -2677,9 +2714,10 @@ def set_letter_literal_handle(self, tokens):
else_suite = condense(colon + trace(attach(simple_compound_stmt, else_handle, copy=True), "else_suite")) | suite
else_stmt = condense(Keyword("else") - else_suite)

match_guard = Optional(Keyword("if").suppress() + test)
full_suite = colon.suppress() + Group((newline.suppress() + indent.suppress() + OneOrMore(stmt) + dedent.suppress()) | simple_stmt)
full_match = trace(attach(
Keyword("match").suppress() + match + Keyword("in").suppress() - test - Optional(Keyword("if").suppress() - test) - full_suite
Keyword("match").suppress() + match + Keyword("in").suppress() - test - match_guard - full_suite
, match_handle), "full_match")
match_stmt = condense(full_match - Optional(else_stmt))

Expand Down Expand Up @@ -2732,9 +2770,9 @@ def set_letter_literal_handle(self, tokens):
name_match_funcdef = Forward()
op_match_funcdef = Forward()
async_match_funcdef = Forward()
name_match_funcdef_ref = name + lparen.suppress() + matchlist_list + rparen.suppress()
name_match_funcdef_ref = name + lparen.suppress() + matchlist_list + match_guard + rparen.suppress()
op_match_funcdef_arg = lparen.suppress() + match + rparen.suppress()
op_match_funcdef_ref = Group(Optional(op_match_funcdef_arg)) + op_funcdef_name + Group(Optional(op_match_funcdef_arg))
op_match_funcdef_ref = Group(Optional(op_match_funcdef_arg)) + op_funcdef_name + Group(Optional(op_match_funcdef_arg)) + match_guard
base_match_funcdef = Keyword("def").suppress() + (op_match_funcdef | name_match_funcdef)
full_match_funcdef = trace(attach(base_match_funcdef + full_suite, full_match_funcdef_handle), "base_match_funcdef")
math_match_funcdef = attach(
Expand Down
Loading

0 comments on commit 7c77590

Please sign in to comment.