Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added function and test cases to check syntax of input equation #155

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
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
35 changes: 34 additions & 1 deletion tests/test_io.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from visma.io.checks import getVariables, areTokensEqual, isTokenInToken
from visma.io.checks import getVariables, areTokensEqual, isTokenInToken, checkSyntax
from visma.io.parser import tokensToString
from visma.io.tokenize import getTerms, normalize
from visma.functions.operator import Operator, Plus
Expand Down Expand Up @@ -54,6 +54,39 @@ def test_isTokenInToken():
assert not isTokenInToken(varA, varE)


def test_checkSyntax():
test1 = False
test2 = False
test3 = False
test4 = False
test5 = False
eqn1 = "2 + sin(2)"
boolean, log = checkSyntax(eqn1)
if (boolean is True and log == "Standard syntax is followed"):
test1 = True
assert test1
eqn2 = "2 + (log2)^(e+2) + sinh(x + x^2)"
boolean, log = checkSyntax(eqn2)
if (boolean is False and log == "For function 'log', arguments are not enclosed within parentheses"):
test2 = True
assert test2
eqn3 = "0.2 + .5"
boolean, log = checkSyntax(eqn3)
if (boolean is False and log == "Decimal point must be between two integers"):
test3 = True
assert test3
eqn4 = "2 + (x+2)(x+3)"
boolean, log = checkSyntax(eqn4)
if (boolean is False and log == "There must be an operator between close parenthesis and open parenthesis"):
test4 = True
assert test4
eqn5 = "2 + (x+2)*(x+3)"
boolean, log = checkSyntax(eqn5)
if (boolean is True and log == "Standard syntax is followed"):
test5 = True
assert test5


#############
# io.parser #
#############
Expand Down
105 changes: 105 additions & 0 deletions visma/io/checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,111 @@ def getVariables(lTokens, rTokens=None, variables=None):
return variables


def checkSyntax(eqnString):
"""Checks if input follows standard syntax or not

Arguments:
eqnString {string} -- equation to be executed

Returns:
bool -- if valid or not
log -- Message for easy debugging
"""
matchingParenthesis = 0 # Counter used to check if any close parenthesis is encountered before open parenthesis
for index, val in enumerate(eqnString):
if (val == '('):
matchingParenthesis += 1
elif (val == ')'):
matchingParenthesis -= 1
if (matchingParenthesis == -1):
log = "Close parenthesis encountered before open parethesis"
return False, log
if (matchingParenthesis != 0):
log = "Each open parenthesis must have a unique corresponding close parenthesis"
return False, log

# Clean equation i.e removing whitespaces
eqnString = eqnString.replace(" ", "")
stringLen = len(eqnString)

# For now explicitly defining function of length 2, as there is only one such
# function and no point in iterating through whole list of function for that
funcOfLen = {2: ['ln'], 3: [], 4: [], 5: []}

# List to check if decimal point is preceded by an integer
integers = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
operators = ['-', '+', '*', '/', '=', '<', '>', '<=', '>=', '^', '(', ',', ';']

for func in funcs:
if (len(func) in [3, 4, 5]):
funcOfLen[len(func)].append(func)
for func in funcSyms:
if (len(func) in [3, 4, 5]):
funcOfLen[len(func)].append(func)

lenCheck = 2 # Length of function to be checked starting from 2
while (lenCheck <= 5):
if (stringLen >= 2): # Any function will need atleast 2 characters to be called. Example simplify(ln)
for index, val in enumerate(eqnString):
if (index < stringLen + 1 - lenCheck):
if (eqnString[index:index+lenCheck] in funcOfLen[lenCheck]):
if (eqnString[index:index+lenCheck+1] in funcOfLen[lenCheck+1]):
if (stringLen < index + lenCheck + 4):
log = "Either the function '{}' has no arguments or arguments are not enclosed within parentheses".format(eqnString[index:index+lenCheck+1])
return False, log
invalidCheck = (eqnString[index+lenCheck+1] != '(')
if invalidCheck:
log = "For function '{}', arguments are not enclosed within parentheses".format(eqnString[index:index+lenCheck+1])
return False, log
invalidCheck = (eqnString[index+lenCheck+2] == ')')
if invalidCheck:
log = "Function '{}' has no argument".format(eqnString[index:index+lenCheck+1])
return False, log
else:
if (stringLen < index + lenCheck + 3):
log = "Either the function '{}' has no arguments or arguments are not enclosed within parentheses".format(eqnString[index:index+lenCheck])
return False, log
invalidCheck = (eqnString[index+lenCheck] != '(')
if invalidCheck:
log = "For function '{}', arguments are not enclosed within parentheses".format(eqnString[index:index+lenCheck])
return False, log
invalidCheck = (eqnString[index+lenCheck+1] == ')')
if invalidCheck:
log = "Function '{}' has no argument".format(eqnString[index:index+lenCheck])
return False, log
else:
break
lenCheck += 1
else:
break

for index, val in enumerate(eqnString):
if (val == '.'):
if (index == 0) or (index == stringLen-1):
log = "Decimal point must be between two integers"
return False, log
invalidCheck = (eqnString[index-1] not in integers or eqnString[index+1] not in integers)
if (invalidCheck):
log = "Decimal point must be between two integers"
return False, log

elif (val == ')'):
if (index < stringLen-2):
invalidCheck1 = (eqnString[index+1] == '(')
invalidCheck2 = False
if (eqnString[index+2] == '('):
invalidCheck2 = (eqnString[index+1] not in operators)
if (invalidCheck1 + invalidCheck2):
log = "There must be an operator between close parenthesis and open parenthesis"
return False, log
elif (index == stringLen-2):
invalidCheck = (eqnString[index+1] in operators and eqnString[index+1] != ';')
if (invalidCheck):
log = "Open parenthesis or an operator cannot be at the end of an equation"
return False, log
return True, "Standard syntax is followed"


def checkEquation(terms, symTokens):
"""Checks if input is a valid expression or equation

Expand Down
4 changes: 3 additions & 1 deletion visma/io/tokenize.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

import math
import copy
from visma.io.checks import isNumber, isVariable, getNumber, checkEquation, funcs, funcSyms
from visma.io.checks import isNumber, isVariable, getNumber, checkEquation, checkSyntax, funcs, funcSyms
from visma.functions.structure import Function, Equation, Expression
from visma.functions.constant import Constant
from visma.functions.variable import Variable
Expand Down Expand Up @@ -1561,6 +1561,8 @@ def tokenizer(eqnString):
Returns:
list -- function tokens list
"""
if (not(checkSyntax(eqnString))[0]):
return
_, tokens = constantConversion(preprocess(eqnString))
return tokens

Expand Down