The Draconic scripting language, designed to allow a safe in-memory runtime for user scripting with bindings to native APIs hosted in the parent application.
Requires Python 3.8+.
The Draconic language is based on, and implemented in, the CPython implementation of the Python language. However, there are a number of notable differences between Draconic and Python:
In Python, in-place operators (e.g. a += 1) are handled differently from binary operators and assignments (
e.g. a = a + 1). In Draconic, all in-place operators are unfurled to their binary-operator equivalent. In most cases
this does not create a semantic difference, except in the following cases:
Python
>>> a = {"a": 1, "b": 2}
>>> b = {"b": 3, "c": 4}
>>> a_reference = a
>>> a |= b
>>> a
{"a": 1, "b": 3, "c": 4}
>>> a_reference
{"a": 1, "b": 3, "c": 4}
>>> a is a_reference
TrueDraconic
>>> a = {"a": 1, "b": 2}
>>> b = {"b": 3, "c": 4}
>>> a_reference = a
>>> a |= b
>>> a
{"a": 1, "b": 3, "c": 4}
>>> a_reference
{"a": 1, "b": 2}
>>> a is a_reference
FalseUse dict.update() or set.update() instead for an in-place operation.
Python
>>> a = 1
>>> def incr_locally():
... a += 1
... return a
>>> incr_locally()
UnboundLocalError: local variable 'a' referenced before assignmentDraconic
>>> a = 1
>>> def incr_locally():
... a += 1
... return a
>>> incr_locally()
2While Python checks that all bindings in a pattern matching case with multiple options are the same across all branches, Draconic does not do any such check.
Python
>>> a = 1
>>> match a:
... case (1 as one) | (2 as two):
... print(one)
SyntaxError: alternative patterns bind different namesDraconic
>>> a = 1
>>> match a:
... case (1 as one) | (2 as two):
... print(one)
1As access to __dunder__ attributes is not allowed in Draconic, the function.__name__ and function.__doc__
attributes are exposed as function.name and function.doc instead.
Python
>>> def foo():
... """I am foo"""
... pass
>>> print(foo.__name__)
foo
>>> print(foo.__doc__)
I am fooDraconic
>>> def foo():
... """I am foo"""
... pass
>>> print(foo.name)
foo
>>> print(foo.doc)
I am fooAs Draconic currently has no user-defined class implementation, it is impossible to provide a type by name to an
except clause. Instead, Draconic requires except clause types to be a string literal or tuple of string literals.
These are matched against the name of the exception, with no subclass checking.
Python
>>> try:
... 1/0
... except Exception:
... print("I catch all exceptions!")
... except ZeroDivisionError:
... print("You divided by zero!")
I catch all exceptions!Draconic
>>> try:
... 1/0
... except "Exception":
... print("I catch all exceptions!")
... except "ZeroDivisionError":
... print("You divided by zero!")
You divided by zero!Assigning to a starred variable outside of a tuple or list in Python throws a SyntaxError, but is valid in Draconic
Python
>>> *a = [1, 2, 3]
SyntaxError: starred assignment target must be in a list or tupleDraconic
>>> *a = [1, 2, 3]
>>> a
[1, 2, 3]This has syntactical equivalents in Python:
>>> *a, = [1, 2, 3]
>>> [*b] = [1, 2, 3]
>>> (*c,) = [1, 2, 3]
>>> assert a == b == c == [1, 2, 3]