Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions palimport/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
# Importing Parser's Grammar importers
from ._lark import Importer as LarkImporter

from ._coconut import Importer as CoconutImporter

from .finder import Finder
from .loader import Loader
Expand Down
34 changes: 34 additions & 0 deletions palimport/_coconut/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from __future__ import absolute_import

import sys
import filefinder2

# we rely on filefinder2 as a py2/3 wrapper of importlib

from .finder import CoconutFinder
from .loader import CoconutLoader


class Importer(filefinder2.Py3Importer):

def __enter__(self):
super(Importer, self).__enter__()

# we hook the grammar customized loader
self.path_hook = CoconutFinder.path_hook((CoconutLoader, ['.coco', '.coc', '.coconut']), )

if self.path_hook not in sys.path_hooks:
ffidx = sys.path_hooks.index(filefinder2.ff_path_hook)
sys.path_hooks.insert(ffidx, self.path_hook )

def __exit__(self, exc_type, exc_val, exc_tb):

# removing path_hook
sys.path_hooks.pop(sys.path_hooks.index(self.path_hook))

super(Importer, self).__exit__(exc_type, exc_val, exc_tb)


__all__ = [
Importer
]
75 changes: 75 additions & 0 deletions palimport/_coconut/finder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
from __future__ import absolute_import, division, print_function

"""
A module to setup custom importer for .lark files

"""

# We need to be extra careful with python versions
# Ref : https://docs.python.org/dev/library/importlib.html#importlib.import_module

# Ref : http://stackoverflow.com/questions/67631/how-to-import-a-module-given-the-full-path
# Note : Couldn't find a way to make imp.load_source deal with packages or relative imports (necessary for our generated message classes)
import os

from filefinder2.machinery import FileFinder as filefinder2_FileFinder

from .._utils import _ImportError


# TODO: This is a commonly used class -> make it easier to use, so that most importers in palimport uses the same code.
class CoconutFinder(filefinder2_FileFinder):
"""PathEntryFinder to handle finding Coconut modules"""

def __init__(self, path, *loader_details):
super(CoconutFinder, self).__init__(path, *loader_details)

def __repr__(self):
return 'CoconutFinder({!r})'.format(self.path)

@classmethod
def path_hook(cls, *loader_details):
"""A class method which returns a closure to use on sys.path_hook
which will return an instance using the specified loaders and the path
called on the closure.

If the path called on the closure is not a directory, or doesnt contain
any files with the supported extension, ImportError is raised.

This is different from default python behavior
but prevent polluting the cache with custom finders
"""
def path_hook_for_CoconutFinder(path):
"""Path hook for importlib.machinery.FileFinder."""

if not (os.path.isdir(path)):
raise _ImportError('only directories are supported')

exts = [x for ld in loader_details for x in ld[1]]
if not any(fname.endswith(ext) for fname in os.listdir(path) for ext in exts):
raise _ImportError(
'only directories containing {ext} files are supported'.format(ext=", ".join(exts)),
path=path)
return cls(path, *loader_details)

return path_hook_for_CoconutFinder

def find_spec(self, fullname, target=None):
"""
Try to find a spec for the specified module.
:param fullname: the name of the package we are trying to import
:return: the matching spec, or None if not found.
"""

# We attempt to load a .lark file as a module
tail_module = fullname.rpartition('.')[2]
base_path = os.path.join(self.path, tail_module)
for suffix, loader_class in self._loaders:
full_path = base_path + suffix
if os.path.isfile(full_path): # maybe we need more checks here (importlib filefinder checks its cache...)
return self._get_spec(loader_class, fullname, full_path, None, target)

# Otherwise, we try find python modules (to be able to embed .lark files within python packages)
return super(CoconutFinder, self).find_spec(fullname=fullname, target=target)


43 changes: 43 additions & 0 deletions palimport/_coconut/loader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@

import filefinder2

from palimport._utils import _verbose_message

from coconut.compiler.compiler import Compiler




class CoconutLoader(filefinder2.machinery.SourceFileLoader):

def __init__(self, fullname, path):
"""Initializes Coconut's compiler"""
self.compiler = Compiler(target=None, strict=True, minify=False, line_numbers=True, keep_lines=True, no_tco=False)
super(CoconutLoader, self).__init__(fullname=fullname, path=path)

# TODO : investigate : removing get_code breaks loader !!!
def get_code(self, fullname):
source = self.get_source(fullname)

_verbose_message('transpiling coconut for "{0}"'.format(fullname))
pysource = self.compiler.parse_file(source, addhash=False) # hash is needed only if we produce bytecode (TODO)

_verbose_message('compiling code for "{0}"'.format(fullname))
try:
code = self.source_to_code(pysource, self.get_filename(fullname))
return code
except TypeError:
raise

def get_source(self, name):
"""Implementing actual python code from file content"""
path = self.get_filename(name)


# pypath = CocoCmd.compile_file(path)

# Returns decoded string from source python file
pystr = super(CoconutLoader, self).get_source(name)

return pystr

Empty file added tests/coconut/__init__.py
Empty file.
7 changes: 7 additions & 0 deletions tests/coconut/fact.coco
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
def factorial(0) = 1

@addpattern(factorial)
def factorial(n is int if n > 0) =
"""Compute n! where n is an integer >= 0."""
range(1, n+1) |> reduce$(*)

30 changes: 30 additions & 0 deletions tests/coconut/test_fact.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import palimport

with palimport.CoconutImporter():
if __package__: # attempting relative import when possible
from . import fact
else:
import fact

import pytest

#import coconut
#import coconut.convenience # should be enough to setup the importer in metapath (actually turning on autocompilation)


def test_fact_neg():
with pytest.raises(fact.MatchError):
fact.factorial(-1)


def test_fact_nint():
with pytest.raises(fact.MatchError):
fact.factorial(0.5)


def test_fact_stop():
assert fact.factorial(0) == 1


def test_fact():
assert fact.factorial(3) == 6