Not really. Language interop is a difficult problem, and language embedding even more so.
Many languages have nontrivial syntax constructs that cannot be easily parsed by a general purpose parser. Perl 5 is the prime example of that: in order to parse a Perl 5 program, you need to execute it. E.g. here is a Perl 5 program that will complain about an extra closing brace 50% of the time:
BEGIN {
print "Running arbitrary code during parsing :)\n";
*f = rand() > 0.5 ? sub () {} : sub ($) {};
}
for ("the lulz") {
f /7 } # /
}
How does this work? We define a function f
with a prototype. The prototype affects parsing of arguments. The ($)
prototype parses one argument, which will here be the result of applying the regex /7 } # /
against $_
. But the ()
prototype does not parse any arguments, so f /7
is a division, and # /
is a comment.
Consider also that some languages like the Lisp family have a completely different concept of “syntax” than C-like curly brace languages. Indentation-sensitive languages like Python or Haskell have no representation in BNF. So syntactic embedding is really difficult. Instead, embedding will typically use some kind of string or here-doc syntax.
The more difficult problem is semantic interoperability. This is fundamentally not a solvable problem, because languages are insanely different. There are simple considerations like memory management. If I pass a garbage-collected object into a language with manual memory management, how will it be disposed? The object might now be referenced from code in both languages, and the garbage collector may be unable to see some references.
Object systems are another area of great differences. In JavaScript and many other dynamic languages, I can patch methods at runtime. How can this work in C++ where method calls may be resolved at compile time? In Java, object initialization order is effectively parent-before-child. How can existing Java classes work when part of a multiple inheritance design in Python? C doesn't even have a first class object system.
There are also a gazillion little type system differences. One language has immutable strings, another doesn't. In C or C++, you cannot call certain functions unless you have a non-const object. Support for variadic functions is inconsistent, varying between “the only kind of function” (Perl) over “special syntax for an array argument” (Java) to “compile-time template expansion” (C++). Some languages have some kind of namespaces, others don't. Some languages support value semantics for user defined types. Generics differ wildly. Overloaded functions. And so on.
The result of these incompatibilities is that there must be some interface layer. Quite often, this must be extended for user-defined types. Either the objects are converted to a different representation, or they are represented by some kind of wrapper object in the target language. Calling an untyped, variadic function from a static language often ends up being quite uncomfortable. It will never feel native.
There are specific examples where some kind of interop does work well. For example, autogenerating language bindings from one source definition like SWIG. Some language platforms like CLR and JVM define a common semantic model that enables easy interoperability for all languages targeting that platform.
There are also some approaches to interoperability that are not invisible, but still work well. For example FFIs, or the Component Object Model. But these generally come with severe semantic restrictions.
I would therefore urge you to abandon your quest for perfect language interoperability between a wide range of languages. Instead, you might have more luck improving interop between two specific languages, e.g. “R in Python” or somesuch. In general, it will be much more difficult to get two languages to interface when they each define their own VM, and easier if the languages can communicate over a simple interface like the C calling convention.
In practice, language interop is so horrendously difficult that in-process interop is often avoided altogether (unless they share a common platform, or one of the languages is C). Instead, inter-process communication over a socket or pipe turns out to be a lot easier. See also: Unix philosophy, microservices.