!cat -n Differentiator.g4

"""
Explanation: Generating Abstract Syntax Trees
Our grammar is stored in the file Differentiator.g4.  The grammar describes arithmetical expression that contain variables.  Furthermore,
the function symbols ln (natural logarithm) and exp (exponential function) are supported.
End of explanation
"""


!antlr4 -Dlanguage=Python3 Differentiator.g4

"""
Explanation: We start by generating both scanner and parser.
End of explanation
"""


from DifferentiatorLexer  import DifferentiatorLexer
from DifferentiatorParser import DifferentiatorParser
import antlr4

%run ../AST-2-Dot.ipynb

"""
Explanation: The files CalculatorLexer.py and CalculatorParser.py contain the generated scanner and parser, respectively.  We have to import these files.  Furthermore, the runtime of 
<span style="font-variant:small-caps;">Antlr</span>
needs to be imported.
End of explanation
"""


def main():
    line = input('> ')
    while line != '':
        input_stream  = antlr4.InputStream(line)
        lexer         = DifferentiatorLexer(input_stream)
        token_stream  = antlr4.CommonTokenStream(lexer)
        parser        = DifferentiatorParser(token_stream)
        context       = parser.expr()
        term          = context.result
        display(tuple2dot(term))
        derivative    = diff(term)
        print(toString(derivative))
        line = input('> ')

"""
Explanation: The function main prompts for an expression that is then parsed and differentiated with respect to the variable x.
End of explanation
"""


def diff(e):
    if isinstance(e, int):
        return '0'
    if e[0] == '+':
        f , g  = e[1:]
        fs, gs = diff(f), diff(g)
        return ('+', fs, gs)
    if e[0] == '-':
        f , g  = e[1:]
        fs, gs = diff(f), diff(g)
        return ('-', fs, gs)
    if e[0] == '*':
        f , g  = e[1:]
        fs, gs = diff(f), diff(g)
        return ('+', ('*', fs, g), ('*', f, gs))
    if e[0] == '/':
        f , g  = e[1:]
        fs, gs = diff(f), diff(g)
        return ('/', ('-', ('*', fs, g), ('*', f, gs)), ('*', g, g))
    if e[0] == 'ln':
        f  = e[1]
        fs = diff(f) 
        return ('/', fs, f)
    if e[0] == 'exp':
        f  = e[1]
        fs = diff(f) 
        return ('*', fs, e)
    if e == 'x':
        return '1'
    return '0'

"""
Explanation: The function diff takes the parse tree e of an arithmetic expression and differentiate this expressions e with respect to the variable x.
End of explanation
"""


def toString(e):
    if isinstance(e, int):
        return str(e)
    if e[0] == '+':
        f, g = e[1:]
        return toString(f) + ' + ' + toString(g)
    if e[0] == '-':
        f, g = e[1:]
        return toString(f) + ' - (' + toString(g) + ')'
    if e[0] == '*':
        f, g = e[1:]
        return parenString(f) + ' * ' + parenString(g)
    if e[0] == '/':
        f, g = e[1:]
        return parenString(f) + ' / (' + toString(g) + ')'
    if e[0] == 'ln':
        return 'ln(' + toString(e[1]) + ')'
    if e[0] == 'exp':
        return 'exp(' + toString(e[1]) + ')'
    return str(e)

"""
Explanation: The function toString takes an arithmetical expression that is represented as a nested tuple and converts it into a string.
End of explanation
"""


def parenString(e):
    if isinstance(e, int):
        return toString(e)
    if e[0] in ['+', '-']:
        return '(' + toString(e) + ')'
    else:
        return toString(e)    

main()

!rm *.py *.tokens *.interp
!rm -r __pycache__/

!ls

"""
Explanation: Convert e into a string that is parenthesized if necessary.
End of explanation
"""


