Pythonberries
4 months ago berry polyglot programming python
Berry is a scripting language tailored to embedded devices; in particular it is available on some builds of Tasmota. It has a syntax that is relatively close to that of Python, but not identical. I recently had cause to write the same program in both, which led me to wonder whether it's possible to write one single program that works in both (that is, a polyglot program).
Let's see what that entails...
The most significant syntactic differences between the two languages - among many other minor ones - are:
-
Berry supports block comments surrounded by
#-...#-, in addition to Python-style single-line comments starting with# -
Berry uses an explicit
endkeyword to end blocks, rather than using indentation
Obviously the aim is to share as much code as possible between the two languages. But for those parts that have to be expressed differently, it's handy to be able to write bits of code that are seen only by one or other of the two languages.
Let's do the easy one first: to write code that is seen by Python but ignored by Berry, we can use Berry's block comment syntax. The open-block (#-) will be taken by Python as a comment, but the close-block won't, so we hide it with another #. That gives us:
#-
print("This will be printed by Python only")
# -#
Now, how about the other way - Berry-only? There's no Python-only comment syntax, but perhaps we can do something similar by using some Python syntax (say, if False:) to disable execution, and then hiding that from Berry with our trick from above. Let's give that a go:
#-
if False:
# -#
print("Only Berry will print this")
This works - Berry will print the message, and Python will ignore it. But it's not ideal: because the Python interpreter is still seeing and interpreting the code, if we need to use any Berry-specific syntax that's not valid Python, the Python interpreter will object to that syntax and reject the whole program, even though we don't need it to even see that part.
So how else can we do this? Another useful way of getting the interpreter to ignore parts of the code, in a way that's more forgiving than an if, is to convince it that the code in question is a string - and Python's multi-line """-strings are very handy for this. We'll use Berry's block comments to hide the string start/end markers from Berry. Here goes:
#-
"""
-#
print("Berry only")
#-
"""
# -#
As before, we need to use an extra # to ensure Python doesn't see the final -#. (We don't need to do that for the first one, because that'll be inside the string markers, so Python won't pay attention to it anyway.)
In fact we can squeeze this up onto fewer lines, making use of the fact that Berry doesn't treat whitespace as significant:
#-
""" -#
print("Berry only")
#- """ # -#
.. and once we've done that, the we can squeeze the last line further, since it no longer needs to be a block quote:
#-
""" -#
print("Berry only")
# """
Putting these tricks together - and economising by not closing a block comment when we're about to open a new one - we can write code that is executed by just Python, just Berry, or both:
#-
print("Python only")
""" -#
print("Berry only")
# """
print("Both")
That's great as far as it goes. But if we're going to write any sort of substantial program, we'll want to be able to use functions and loops. The syntax for these is tantalisingly similar between the two languages, but not identical: in particular Python requires a : after the def or while, and Berry requires an end at the end - and neither language is tolerant of the other's syntax.
Let's see how we can use the tricks we learned above to accommodate these differences. First, a function:
def myfunc(a, b #-
):
""" -# ) #- """ # -#
print("Sum is", a+b)
#-
""" -#
end
# """
To hide Python's colon from Berry, we use the Berry block quote. But we can't do that all on one line: Berry would be quite happy with that, but Python would take the #- as the start of a comment, and so it wouldn't see the colon either. But if we try to move the colon to a later line, Python complains of a syntax error. How can we get round this?
Python will allow us to have the colon on a later line, if we also move the close-parenthesis. So we can get out of this fix by having two close parentheses - one with a colon, one without - and hiding each from one of our languages using the tricks from above.
We also need to hide the end from Python. The trick from earlier works fine for that - but at the expense of a bunch of noise at the end of every function. Can we do better? It turns out we can: we don't need to hide end from Python, just ensure it has no effect; just defining it as an identifier (say, by setting it to None) is enough. We'll have to hide that assignment from Berry, where end is a reserved word, but we're well practiced at that by now!
The same techniques that let us define a function work for a while loop. Here's how that looks using our new end technique:
#- Set up 'end' so Python won't complain
end=None
# -#
x=0
while (x < 10 #-
):
""" -# ) # """
print(x)
x = x + 1
end
While this doesn't cover every situation you might encounter while writing one program for the two languages (for example, it doesn't cover iterating over lists), it's enough to get going - and enough for the example project I had in mind when I started. Whether it's sensible is another matter!
0 comments