Skip to content

Commit

Permalink
First commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
progval committed Jan 18, 2015
0 parents commit 7259eda
Show file tree
Hide file tree
Showing 14 changed files with 481 additions and 0 deletions.
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
*~
*.swp
*.swo
*.dot
*.ps
__pycache__
build/
dist/
*.egg-info/
.coverage
htmlcov
parser.out
parsetab.py
2 changes: 2 additions & 0 deletions .pylintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/bash
pylint --rcfile=.pylintrc ppp_natural_math
10 changes: 10 additions & 0 deletions .scrutinizer.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
checks:
python:
code_rating: true
duplicate_code: true

tools:
# pylint:
# python_version: 3
# config_file: '.pylintrc'
external_code_coverage: true
20 changes: 20 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

language: python

python:
- 3.2
- 3.3
- 3.4
- pypy3

install:
- pip install scrutinizer-ocular coverage webtest httmock requests ppp_datamodel ppp_libmodule ply

before_script:
- ./setup.py install

script:
- coverage3 run run_tests.py

after_script:
- ocular --data-file ".coverage"
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2015 Valentin Lorentz

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
18 changes: 18 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

PYTHON=python3

all: install

install:
$(PYTHON) setup.py install

localinstall:
$(PYTHON) setup.py install --user

run:
gunicorn ppp_natural_math:app

tests:
$(PYTHON) run_tests.py

.PHONY: all install localinstall tests
4 changes: 4 additions & 0 deletions coverage.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash
python3-coverage run --source=ppp_natural_math run_tests.py
python3-coverage html
xdg-open htmlcov/index.html
10 changes: 10 additions & 0 deletions ppp_natural_math/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""Natural language processing for math questions for the Projet Pensées
Profondes."""

from ppp_libmodule import HttpRequestHandler
from .requesthandler import RequestHandler

def app(environ, start_response):
"""Function called by the WSGI server."""
return HttpRequestHandler(environ, start_response, RequestHandler) \
.dispatch()
281 changes: 281 additions & 0 deletions ppp_natural_math/parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
from ply import lex, yacc

reserved = {
'sum': 'SUM',
'product': 'PRODUCT',
'integrate': 'INTEGRATE',
'from': 'FROM',
'to': 'TO',
'of': 'OF',
}

tokens = (
'SUM',
'PRODUCT',
'INTEGRATE',
'FROM',
'TO',
'OF',
'INFIX',
'LEFT_PAREN',
'RIGHT_PAREN',
'NAME',
'NATURAL',
'NUMBER',
'UNDERSCORE',
)

t_LEFT_PAREN = r'\('
t_RIGHT_PAREN = r'\)'
def t_NAME(t):
r'(?i)[a-z][a-z0-9]*'
t.type = reserved.get(t.value.lower(), 'NAME')
return t
t_NATURAL = r'[1-9][0-9]*'
t_NUMBER = r'-?([0-9]*[.,])?[0-9]+'
t_INFIX = r'[-+*/^]'
t_UNDERSCORE = r'_'

t_ignore = r' '

def t_error(t):
raise ParserException('Illegal string `%s`' % t.value)

lexer = lex.lex()

class ParserException(Exception):
pass
class CannotGuessVariable(ParserException):
pass

def guess_variable(expression, hint):
free_vars = expression.free_vars()
for name in hint:
if name in free_vars:
return name
raise CannotGuessVariable(expression, hint)


class Variable:
def __init__(self, name):
if not isinstance(name, str):
raise ValueError('%r is not a string.' % (name,))
self._name = name

@property
def name(self):
return self._name

def free_vars(self):
return {self.name}

def __eq__(self, other):
if not isinstance(other, Variable):
return False
return self.name == other.name
def __hash__(self):
return hash(self.name)
def __repr__(self):
return 'Variable(%r)' % self.name

def output(self):
return self.name

class Infix:
def __init__(self, left, op, right):
self._left = left
self._op = op
self._right = right

@property
def left(self):
return self._left
@property
def op(self):
return self._op
@property
def right(self):
return self._right

def free_vars(self):
return self.left.free_vars() | self.right.free_vars()

def __eq__(self, other):
return (self.left, self.op, self.right) == \
(other.left, other.op, other.right)
def __hash__(self):
return hash((self.left, self.op, self.right))
def __repr__(self):
return 'Infix(%r, %r, %r)' % \
(self.left, self.op, self.right)

def output(self):
return '%s%s%s' % (self.left.output(), self.op, self.right.output())

class Number:
def __init__(self, value):
self._value = value

@property
def value(self):
return self._value

def free_vars(self):
return set()

def __eq__(self, other):
if not isinstance(other, Number):
return False
return self.value == other.value

def __hash__(self):
return hash(self.value)

def __repr__(self):
return 'Number(%r)' % self.value

def output(self):
return str(self.value)

class TwoBounds:
def __init__(self, expr, var=None, from_=None, to=None):
self._expr = expr
self._var = var or guess_variable(expr, self._variable_hint)
self._from = from_
self._to = to
if not isinstance(self.var, str):
raise ValueError('%r is not a string' % self.var)

@property
def expr(self):
return self._expr
@property
def var(self):
return self._var
@property
def from_(self):
return self._from
@property
def to(self):
return self._to

def free_vars(self):
return self.expr.free_vars() - {self.var}

def __eq__(self, other):
if not isinstance(other, self.__class__):
return False
return (self.expr, self.var, self.from_, self.to) == \
(other.expr, other.var, other.from_, other.to)

def __hash__(self):
return hash((other.expr, other.var, other.from_, other.to))

def __repr__(self):
return '%s(%r, %r, %r, %r)' % (self.__class__.__name__,
self.expr, self.var, self.from_, self.to)

def output(self):
if self.from_ and self.to:
return '%s(%s, %s, %s, %s)' % (self.__class__.__name__,
self.expr.output(), self.var,
self.from_.output(), self.to.output())
else:
return '%s(%s, %s)' % (self.__class__.__name__,
self.expr.output(), self.var)

class Integrate(TwoBounds):
_variable_hint = 'wvutzyx'
class Sum(TwoBounds):
_variable_hint = 'lkji'
class Product(TwoBounds):
_variable_hint = 'lkji'


###################################################
# Variables
def p_variable_name(t):
'''variable : NAME'''
t[0] = Variable(t[1])
def p_variable_underscore(t):
'''variable : variable UNDERSCORE NATURAL'''
t[0] = Variable('%s_%d' % (t[1].name, t[3]))
def p_expression_variable(t):
'''expression : variable'''
t[0] = t[1]

###################################################
# Misc
def p_expression_number(t):
'''expression : NUMBER'''
t[0] = Number(float(t[1]))
def p_expression_infix(t):
'''expression : expression INFIX expression'''
t[0] = Infix(t[1], t[2], t[3])
def p_fromto(t):
'''fromto : FROM expression TO expression'''
t[0] = (t[2], t[4])

###################################################
# Sum
def p_sum_base(t):
'''sum : SUM expression'''
t[0] = Sum(t[2])
def p_sum_base2(t):
'''sum : SUM OF expression'''
t[0] = Sum(t[3])
# TODO: Shift/reduce conflict
def p_expression_sum(t):
'''expression : sum'''
t[0] = t[1]
def p_expression_sum_fromto(t):
'''expression : sum fromto'''
t[0] = Sum(t[1].expr, var=t[1].var, from_=t[2][0], to=t[2][1])

###################################################
# Product
def p_product_base(t):
'''product : PRODUCT expression'''
t[0] = Product(t[2])
def p_product_base2(t):
'''product : PRODUCT OF expression'''
t[0] = Product(t[3])
# TODO: Shift/reduce conflict
def p_expression_product(t):
'''expression : product'''
t[0] = t[1]
def p_expression_product_fromto(t):
'''expression : product fromto'''
t[0] = Product(t[1].expr, var=t[1].var, from_=t[2][0], to=t[2][1])

###################################################
# Integrate
def p_integrate_base(t):
'''integrate : INTEGRATE expression'''
t[0] = Integrate(t[2])
def p_integrate_base2(t):
'''integrate : INTEGRATE OF expression'''
t[0] = Integrate(t[3])
# TODO: Shift/reduce conflict
def p_expression_integrate(t):
'''expression : integrate'''
t[0] = t[1]
def p_expression_integrate_fromto(t):
'''expression : integrate fromto'''
t[0] = Integrate(t[1].expr, var=t[1].var, from_=t[2][0], to=t[2][1])


def p_error(t):
if t is None:
raise ParserException('Unknown PLY error.')
else:
raise ParserException("Syntax error at '%s' (%s)" %
(t.value, t.type))

parser = yacc.yacc(start='expression')

def build_tree(s):
return parser.parse(s)

def translate(s):
return build_tree(s).output()
Loading

0 comments on commit 7259eda

Please sign in to comment.