Skip to content

Commit

Permalink
Merge f8842a9 into 04767ab
Browse files Browse the repository at this point in the history
  • Loading branch information
StrikerRUS committed Apr 12, 2020
2 parents 04767ab + f8842a9 commit d7b925e
Show file tree
Hide file tree
Showing 35 changed files with 768 additions and 89 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Expand Up @@ -8,7 +8,7 @@ python:

env:
- TEST=API
- TEST=E2E LANG="c_lang or python or java or go_lang or javascript or php"
- TEST=E2E LANG="c_lang or python or java or go_lang or javascript or php or haskell"
- TEST=E2E LANG="c_sharp or visual_basic or powershell"
- TEST=E2E LANG="r_lang or dart"

Expand Down
6 changes: 6 additions & 0 deletions .travis/setup.sh
Expand Up @@ -37,3 +37,9 @@ if [[ $LANG == *"dart"* ]]; then
sudo apt-get update
sudo apt-get install --no-install-recommends -y dart
fi

# Install Haskell.
if [[ $LANG == *"haskell"* ]]; then
sudo apt-get update
sudo apt-get install --no-install-recommends -y haskell-platform
fi
3 changes: 2 additions & 1 deletion Dockerfile
Expand Up @@ -26,7 +26,8 @@ RUN apt-get update && \
powershell \
r-base \
php \
dart && \
dart \
haskell-platform && \
rm -rf /var/lib/apt/lists/*

WORKDIR /m2cgen
Expand Down
3 changes: 2 additions & 1 deletion README.md
Expand Up @@ -7,7 +7,7 @@
[![PyPI Version](https://img.shields.io/pypi/v/m2cgen.svg?logo=pypi&logoColor=white)](https://pypi.org/project/m2cgen)
[![Downloads](https://pepy.tech/badge/m2cgen)](https://pepy.tech/project/m2cgen)

**m2cgen** (Model 2 Code Generator) - is a lightweight library which provides an easy way to transpile trained statistical models into a native code (Python, C, Java, Go, JavaScript, Visual Basic, C#, PowerShell, R, PHP, Dart).
**m2cgen** (Model 2 Code Generator) - is a lightweight library which provides an easy way to transpile trained statistical models into a native code (Python, C, Java, Go, JavaScript, Visual Basic, C#, PowerShell, R, PHP, Dart, Haskell).

* [Installation](#installation)
* [Supported Languages](#supported-languages)
Expand All @@ -30,6 +30,7 @@ pip install m2cgen
- C#
- Dart
- Go
- Haskell
- Java
- JavaScript
- PHP
Expand Down
2 changes: 2 additions & 0 deletions m2cgen/__init__.py
Expand Up @@ -12,6 +12,7 @@
export_to_r,
export_to_php,
export_to_dart,
export_to_haskell,
)

__all__ = [
Expand All @@ -26,6 +27,7 @@
export_to_r,
export_to_php,
export_to_dart,
export_to_haskell,
]

with open(os.path.join(os.path.dirname(os.path.realpath(__file__)),
Expand Down
2 changes: 2 additions & 0 deletions m2cgen/cli.py
Expand Up @@ -32,6 +32,8 @@
"r": (m2cgen.export_to_r, ["indent", "function_name"]),
"php": (m2cgen.export_to_php, ["indent", "function_name"]),
"dart": (m2cgen.export_to_dart, ["indent", "function_name"]),
"haskell": (m2cgen.export_to_haskell,
["module_name", "indent", "function_name"]),
}


Expand Down
28 changes: 28 additions & 0 deletions m2cgen/exporters.py
Expand Up @@ -326,6 +326,34 @@ def export_to_dart(model, indent=4, function_name="score"):
return _export(model, interpreter)


def export_to_haskell(model, module_name="Model", indent=4,
function_name="score"):
"""
Generates a Haskell code representation of the given model.
Parameters
----------
model : object
The model object that should be transpiled into code.
module_name : string, optional
The name of the generated module.
indent : int, optional
The size of indents in the generated code.
function_name : string, optional
Name of the function in the generated code.
Returns
-------
code : string
"""
interpreter = interpreters.HaskellInterpreter(
module_name=module_name,
indent=indent,
function_name=function_name,
)
return _export(model, interpreter)


def _export(model, interpreter):
assembler_cls = assemblers.get_assembler_cls(model)
model_ast = assembler_cls(model).assemble()
Expand Down
2 changes: 2 additions & 0 deletions m2cgen/interpreters/__init__.py
Expand Up @@ -9,6 +9,7 @@
from .r.interpreter import RInterpreter
from .php.interpreter import PhpInterpreter
from .dart.interpreter import DartInterpreter
from .haskell.interpreter import HaskellInterpreter

__all__ = [
JavaInterpreter,
Expand All @@ -22,4 +23,5 @@
RInterpreter,
PhpInterpreter,
DartInterpreter,
HaskellInterpreter,
]
4 changes: 2 additions & 2 deletions m2cgen/interpreters/c/interpreter.py
Expand Up @@ -3,10 +3,10 @@
from m2cgen import ast
from m2cgen.interpreters import utils, mixins
from m2cgen.interpreters.c.code_generator import CCodeGenerator
from m2cgen.interpreters.interpreter import ToCodeInterpreter
from m2cgen.interpreters.interpreter import ImperativeToCodeInterpreter


class CInterpreter(ToCodeInterpreter,
class CInterpreter(ImperativeToCodeInterpreter,
mixins.LinearAlgebraMixin):

supported_bin_vector_ops = {
Expand Down
5 changes: 3 additions & 2 deletions m2cgen/interpreters/c_sharp/interpreter.py
Expand Up @@ -3,11 +3,12 @@
from m2cgen import ast
from m2cgen.interpreters import mixins
from m2cgen.interpreters import utils
from m2cgen.interpreters.interpreter import ToCodeInterpreter
from m2cgen.interpreters.interpreter import ImperativeToCodeInterpreter
from m2cgen.interpreters.c_sharp.code_generator import CSharpCodeGenerator


class CSharpInterpreter(ToCodeInterpreter, mixins.LinearAlgebraMixin):
class CSharpInterpreter(ImperativeToCodeInterpreter,
mixins.LinearAlgebraMixin):

supported_bin_vector_ops = {
ast.BinNumOpType.ADD: "AddVectors",
Expand Down
80 changes: 48 additions & 32 deletions m2cgen/interpreters/code_generator.py
Expand Up @@ -25,28 +25,16 @@ class BaseCodeGenerator:

tpl_num_value = NotImplemented
tpl_infix_expression = NotImplemented
tpl_var_declaration = NotImplemented
tpl_return_statement = NotImplemented
tpl_if_statement = NotImplemented
tpl_else_statement = NotImplemented
tpl_array_index_access = NotImplemented
tpl_block_termination = NotImplemented
tpl_var_assignment = NotImplemented

def __init__(self, indent=4):
self._indent = indent
self.reset_state()

def reset_state(self):
self._current_indent = 0
self._var_idx = 0
self.code = ""

def get_var_name(self):
var_name = "var" + str(self._var_idx)
self._var_idx += 1
return var_name

def increase_indent(self):
self._current_indent += self._indent

Expand Down Expand Up @@ -78,6 +66,53 @@ def prepend_code_lines(self, lines):
for l in lines[::-1]:
self.prepend_code_line(l)

# Following methods simply compute expressions using templates without
# changing result.

def infix_expression(self, left, right, op):
return self.tpl_infix_expression(left=left, right=right, op=op)

def num_value(self, value):
return self.tpl_num_value(value=value)

def array_index_access(self, array_name, index):
return self.tpl_array_index_access(
array_name=array_name, index=index)

def function_invocation(self, function_name, *args):
return function_name + "(" + ", ".join(map(str, args)) + ")"

# Helpers

def _comp_op_overwrite(self, op):
return op.value


class ImperativeCodeGenerator(BaseCodeGenerator):
"""
This class provides basic functionality to generate code. It is
language-agnostic, but exposes set of attributes which subclasses should
use to define syntax specific for certain language(s).
!!IMPORTANT!!: Code generators must know nothing about AST.
"""

tpl_var_declaration = NotImplemented
tpl_return_statement = NotImplemented
tpl_if_statement = NotImplemented
tpl_else_statement = NotImplemented
tpl_block_termination = NotImplemented
tpl_var_assignment = NotImplemented

def reset_state(self):
super().reset_state()
self._var_idx = 0

def get_var_name(self):
var_name = "var" + str(self._var_idx)
self._var_idx += 1
return var_name

# Following statements compute expressions using templates AND add
# it to the result.

Expand Down Expand Up @@ -110,32 +145,13 @@ def add_var_assignment(self, var_name, value, value_size):
self.add_code_line(
self.tpl_var_assignment(var_name=var_name, value=value))

# Following methods simply compute expressions using templates without
# changing result.

def infix_expression(self, left, right, op):
return self.tpl_infix_expression(left=left, right=right, op=op)

def num_value(self, value):
return self.tpl_num_value(value=value)

def array_index_access(self, array_name, index):
return self.tpl_array_index_access(
array_name=array_name, index=index)

def function_invocation(self, function_name, *args):
return function_name + "(" + ", ".join(map(str, args)) + ")"

# Helpers

def _get_var_declare_type(self, expr):
return NotImplemented

def _comp_op_overwrite(self, op):
return op.value


class CLikeCodeGenerator(BaseCodeGenerator):
class CLikeCodeGenerator(ImperativeCodeGenerator):
"""
This code generator provides C-like syntax so that subclasses will only
have to provide logic for wrapping expressions into functions/classes/etc.
Expand Down
4 changes: 2 additions & 2 deletions m2cgen/interpreters/dart/interpreter.py
Expand Up @@ -3,11 +3,11 @@
from m2cgen import ast
from m2cgen.interpreters import mixins
from m2cgen.interpreters import utils
from m2cgen.interpreters.interpreter import ToCodeInterpreter
from m2cgen.interpreters.interpreter import ImperativeToCodeInterpreter
from m2cgen.interpreters.dart.code_generator import DartCodeGenerator


class DartInterpreter(ToCodeInterpreter,
class DartInterpreter(ImperativeToCodeInterpreter,
mixins.LinearAlgebraMixin,
mixins.BinExpressionDepthTrackingMixin):

Expand Down
5 changes: 3 additions & 2 deletions m2cgen/interpreters/go/code_generator.py
@@ -1,9 +1,10 @@
import contextlib

from m2cgen.interpreters.code_generator import BaseCodeGenerator, CodeTemplate
from m2cgen.interpreters.code_generator \
import ImperativeCodeGenerator, CodeTemplate


class GoCodeGenerator(BaseCodeGenerator):
class GoCodeGenerator(ImperativeCodeGenerator):
tpl_num_value = CodeTemplate("${value}")
tpl_infix_expression = CodeTemplate("(${left}) ${op} (${right})")
tpl_array_index_access = CodeTemplate("${array_name}[${index}]")
Expand Down
4 changes: 2 additions & 2 deletions m2cgen/interpreters/go/interpreter.py
Expand Up @@ -3,10 +3,10 @@
from m2cgen import ast
from m2cgen.interpreters import mixins, utils
from m2cgen.interpreters.go.code_generator import GoCodeGenerator
from m2cgen.interpreters.interpreter import ToCodeInterpreter
from m2cgen.interpreters.interpreter import ImperativeToCodeInterpreter


class GoInterpreter(ToCodeInterpreter,
class GoInterpreter(ImperativeToCodeInterpreter,
mixins.LinearAlgebraMixin):
supported_bin_vector_ops = {
ast.BinNumOpType.ADD: "addVectors",
Expand Down
2 changes: 1 addition & 1 deletion m2cgen/interpreters/go/linear_algebra.go
Expand Up @@ -11,4 +11,4 @@ func mulVectorNumber(v1 []float64, num float64) []float64 {
result[i] = v1[i] * num
}
return result
}
}
Empty file.
81 changes: 81 additions & 0 deletions m2cgen/interpreters/haskell/code_generator.py
@@ -0,0 +1,81 @@
import contextlib

from m2cgen.ast import CompOpType
from m2cgen.interpreters.code_generator import BaseCodeGenerator, CodeTemplate


class HaskellCodeGenerator(BaseCodeGenerator):
tpl_num_value = CodeTemplate("${value}")
tpl_infix_expression = CodeTemplate("(${left}) ${op} (${right})")
tpl_module_definition = CodeTemplate("module ${module_name} where")

def __init__(self, *args, **kwargs):
self._func_idx = 0
super().__init__(*args, **kwargs)

def reset_state(self):
super().reset_state()
self._func_idx = 0

def array_index_access(self, array_name, index):
return self.tpl_infix_expression(
left=array_name, op="!!", right=index)

def add_if_statement(self, if_def):
self.add_code_line("if ({})".format(if_def))
self.increase_indent()
self.add_code_line("then")
self.increase_indent()

def add_else_statement(self):
self.decrease_indent()
self.add_code_line("else")
self.increase_indent()

def add_if_termination(self):
self.decrease_indent()
self.decrease_indent()

def get_func_name(self):
func_name = "func" + str(self._func_idx)
self._func_idx += 1
return func_name

def add_function(self, function_name, function_body):
self.add_code_line("{} =".format(function_name))
self.increase_indent()
self.add_code_lines(function_body)
self.decrease_indent()

def function_invocation(self, function_name, *args):
return (function_name + " " +
" ".join(map(lambda x: "({})".format(x), args)))

def add_function_def(self, name, args, is_scalar_output):
signature = name + " :: "
signature += " -> ".join(
["[Double]" if is_vector else "Double"
for is_vector, _ in [*args, (not is_scalar_output, None)]])
self.add_code_line(signature)

function_def = name + " "
function_def += " ".join([n for _, n in args])
function_def += " ="
self.add_code_line(function_def)

self.increase_indent()

@contextlib.contextmanager
def function_definition(self, name, args, is_scalar_output):
self.add_function_def(name, args, is_scalar_output)
yield
self.decrease_indent()

def vector_init(self, values):
return "[" + ", ".join(values) + "]"

def _comp_op_overwrite(self, op):
if op == CompOpType.NOT_EQ:
return "/="
else:
return op.value

0 comments on commit d7b925e

Please sign in to comment.