Description
Here is a design to get started:
from lpython import S
from sympy import Symbol
def f():
x: S = Symbol("x")
y: S = Symbol("y")
z: S = x + y
print(z)
f()
In ASR, this gets represented using a new SymbolicExpression
type (and use S
in Python code, to keep things short). Later we can add more subtypes of S
, such as Symbol
, SAdd
, STimes
, SInteger
, etc., as well as casting from S
to some of these types (checked in Debug mode), so that one can write functions that only accept those, but for now we'll just use S
. The +
becomes SymbolicAdd
, an IntrinsicFunction
accepting two SymbolicExpression
arguments. The Symbol
becomes SymbolicSymbol(str) -> SymbolicExpression
, also an InstrinsicFunction
.
In the IntrinsicFunction rewriting pass we implement all these by calling into C's API of SymEngine (https://github.com/symengine/symengine/blob/2b575b9be9bb499d866dc3e411e6368ca0d1bb42/symengine/tests/cwrapper/test_cwrapper.c#L19), effectively transforming the code to something like:
basic x = symbol("x')
basic y = symbol("y")
basic z = add(x, y)
printf(str(z))
free(z)
free(x)
free(y)
We can use any backend, such as the LLVM backend to compile this to an object file. Then at link time we have to link in the SymEngine library (and C++ runtime library).
We probably set the allocatable
attribute for x
which is a Variable
of type SymbolicExpression
, and treat it like an allocatable array or a string. We will reuse our existing allocatable
mechanism that ensures that things get properly deallocated when they get out of scope.
This minimal design does not close any doors and we can later extend it in various ways, as needed (such as compile time simplification/evaluation, many subtypes, adding more dedicated ASR nodes if needed, various ASR optimizations, etc.).