#python #transpiler #programming-language #compiler

bin+lib mamba

将 Mamba 文件转换为 Python 3 文件的转换器

11 个版本

0.3.6 2023 年 1 月 21 日
0.3.5 2022 年 12 月 28 日
0.3.3 2022 年 6 月 8 日
0.3.0 2020 年 3 月 3 日
0.2.0 2019 年 11 月 20 日

#667 in 解析器实现

Download history 8/week @ 2024-06-29 55/week @ 2024-07-27

63 每月下载量

自定义许可GPL-3.0+

740KB
16K SLoC

Mamba logo

GitHub Workflow Status Codecov coverage Crate
License Active milestones Built with Love

蟒蛇

这是蟒蛇编程语言。蟒蛇类似于 Python,但有一些关键特性

  • 严格的静态类型规则,但具有类型推断,因此不会过多地阻碍
  • 类型精炼特性
  • 空值安全
  • 显式错误处理
  • 可变性和不可变性的区别
  • 纯函数,或者,无副作用的函数

这是一个用 Rust 编写的转换器,它将 Mamba 源文件转换为 Python 源文件。因此,Mamba 代码应与 Python 代码兼容。可以在 Mamba 中调用 Python 编写的函数,反之亦然(从生成的 Python 文件中)。

⌨️ 代码示例

以下是几个代码示例,以展示 Mamba 的功能。我们强调函数的工作方式、如何定义类、如何应用类型和类型精炼特性、如何使用 Mamba 确保纯度,以及错误处理的工作方式。

➕ 函数

我们可以编写一个简单的脚本,计算用户给出的值的阶乘。

def factorial(x: Int) -> Int => match x
    0 => 1
    n => n * factorial(n - 1)

def num := input("Compute factorial: ")
if num.is_digit() then
    def result := factorial(Int(num))
    print("Factorial {num} is: {result}.")
else
    print("Input was not an integer.")

注意,在这里我们通过写入 x: Int 来指定参数 x 的类型,在这种情况下是一个 Int,这意味着编译器将为我们检查阶乘是否仅用于整数参数。

注意 可以在上面的示例中使用 动态规划,以减少内存消耗

def factorial(x: Int) -> Int => match x
    0 => 1
    n =>
        def ans := 1
        for i in 1 ..= n do ans := ans * i
        ans

📋 类型、类和可变性

类与 Python 中的类类似,但我们可以通过声明函数是可变的还是不可变的来指定我们是否可以写入 self。如果我们写入 self,它是可变的,而如果我们写入 fin self,它是不可变的,并且我们不能更改其字段。我们也可以对任何字段做同样的事情。我们使用一个简单的模拟 Server 对象来展示这一点。

from ipaddress import IPv4Address

class ServerError(def message: Str): Exception(message)

def fin always_the_same_message := "Connected!"

class MyServer(def ip_address: IPv4Address)
    def is_connected: Bool  := False
    def _last_message: Str  := "temp"

    def last_sent(fin self) -> Str raise [ServerError] =>
        self._last_message

    def connect(self) =>
        self.is_connected := True
        print(always_the_same_message)

    def send(self, message: Str) raise [ServerError] =>
        if self.is_connected then
            self._last_message := message
        else
            raise ServerError("Not connected!")

    def disconnect(self) => self.is_connected := False

注意,在 self 中,last_sent 不可变,这意味着我们只能读取变量,而在 connect 中 self 是可变的,因此我们可以更改 self 的属性。然后我们可以这样使用 MyServer

import ipaddress
from server import MyServer

def fin some_ip := ipaddress.ip_address("151.101.193.140")
def my_server   := MyServer(some_ip)

http_server.connect()
if my_server.is_connected then http_server.send("Hello World!")

# This statement may raise an error, but for now de simply leave it as-is
# See the error handling section for more detail
print("last message sent before disconnect: \"{my_server.last_sent()}\".")
my_server.disconnect()

🗃 类型细化(🇻 0.4.1+)

如上所示,Mamba 有类型系统。然而,Mamba 还具有类型细化功能,可以为类型分配额外的属性。让我们扩展上面的服务器示例,并稍作修改

from ipaddress import IPv4Address

type ConnMyServer: MyServer when self.is_connected
type DisConnMyServer: MyServer when not self.is_connected

class ServerErr(def message: Str): Exception(message)

class MyServer(self: DisConnMyServer, def ip_address: IPv4Address)
    def is_connected: Bool  := False
    def _last_message: Str? := None

    def last_sent(self) -> Str raise [ServerErr] => 
        if self.last_message != None then 
            self._last_message
        else
            raise ServerError("No last message!")

    def connect(self: DisConnMyServer) => self.is_connected := True

    def send(self: ConnMyServer, message: Str) => self._last_message := message

    def disconnect(self: ConnMyServer) => self.is_connected := False

在 if 语句的 then 分支中,我们知道 self._last_message 是一个 Str。这是因为我们在 if 条件中执行了检查。

注意上面,我们如何定义 self 的类型。每种类型实际上表示 self 可能处于的另一种状态。对于每种类型,我们使用 when 来显示它是具有某些条件的类型细化。

import ipaddress
from server import MyServer

def fin some_ip := ipaddress.ip_address("151.101.193.140")
def my_server   := MyServer(some_ip)

# The default state of http_server is DisconnectedHTTPServer, so we don't need to check that here
http_server.connect()

# We check the state
if my_server isa ConnMyServer then
    # http_server is a Connected Server if the above is true
    my_server.send("Hello World!")

print("last message sent before disconnect: \"{my_server.last_sent}\".")
if my_server isa ConnectedMyServer then my_server.disconnect()

类型细化还允许我们指定函数的定义域和值域,例如,只接受和返回正整数的函数。

type PosInt: Int when 
    self >= 0 else "Must be greater than 0"

def factorial(x: PosInt) -> PosInt => match x
    0 => 1
    n => n * factorial(n - 1)

简而言之,类型允许我们根据输入类型指定函数的定义域和值域,例如,IntStr。在执行过程中,会进行检查以验证变量是否符合细化类型的规范。如果不符,将引发异常。

类型细化还可以让我们做一些额外的事情。

  • 它可以进一步指定函数的定义域或值域。
  • 它可以明确命名对象的可能状态。这意味着我们不必不断检查某些条件是否成立。我们只需检查给定对象是否处于某种状态,即检查它是否是某种类型。

🔒 纯函数(🇻 0.4.1+)

Mamba 有确保函数纯度的功能,这意味着对于任何 f,如果 x = y,则 f(x) = f(y)。除非函数的输出是 NoneNaN。默认情况下,函数不是纯的,可以读取任何它们想要的变量,如 Python。当我们使函数 pure 时,它不能

  • 读取 self 的非最终属性。
  • 调用不纯函数。

有一些规则适用于调用和分配传递的参数,以保持纯属性(意味着没有副作用)

  • 函数体内定义的任何内容都是公平的游戏,它可以被任何方式使用,因为它将在函数退出时被销毁。
  • 可以将参数赋值,因为这不会修改原始引用。
  • 不能将参数的字段赋值,因为这会修改原始引用。
  • 只能读取参数的最终字段(fin)。
  • 只能调用参数的纯方法(pure)。

当函数是 pure 时,对于给定的输入,它的输出始终相同。它也没有副作用,这意味着它不能写入任何内容(将值分配给可变变量)或从它们读取。不可变变量和纯函数使编写无隐藏依赖的声明性程序变得更容易。

# taylor is immutable, its value does not change during execution
def fin taylor := 7

# the sin function is pure, its output depends solely on the input
def pure sin(x: Int) =>
    def ans := x
    for i in 1 ..= taylor .. 2 do
        ans := ans + (x ^ (i + 2)) / (factorial (i + 2))
    ans

⚠ 错误处理

与 Python 不同,Mamba 没有使用 try exceptfinally(或者有时称为 try catch)。相反,我们旨在直接现场处理错误,以便更容易追踪错误的来源。以下只是一个简要的示例。

我们可以修改上面的脚本,使其不检查服务器是否已连接。在这种情况下,我们必须处理 my_server 抛出 ServerErr 的情况。

import ipaddress
from server import MyServer

def fin some_ip := ipaddress.ip_address("151.101.193.140")
def my_server   := MyServer(some_ip)

def message := "Hello World!"
my_server.send(message) handle
    err: ServerErr => print("Error while sending message: \"{message}\": {err}")

if my_server isa ConnectedMyServer then my_server.disconnect()

在上面的脚本中,我们会一直打印错误,因为我们忘记实际连接到服务器。在这里,我们展示了如何现场处理错误,而不是在(大的)try块中处理。这意味着我们不需要finally块:我们的目标是处理错误发生的地方,然后继续执行剩余的代码。这也防止了我们将大代码块包裹在try中,那里可能不清楚哪个语句或表达式可能会抛出什么错误。

handle也可以与赋值结合使用。在这种情况下,我们必须要么始终返回(终止执行或退出函数),要么计算出一个值。下面是示例:

def g() =>
    def a := function_may_throw_err() handle
        err: MyErr =>
            print("We have a problem: {err.message}.")
            return  # we return, halting execution
        err: MyOtherErr =>
            print("We have another problem: {err.message}.")
            0  # ... or we assign default value 0 to a

    print("a has value {a}.")

如果我们不想使用handle,我们可以在语句或异常后简单地使用raise来表示其执行可能会产生异常,但我们不想在这里处理它。请参见上面的部分,其中我们未处理错误,而是简单地使用raise传递。

💻 命令行界面

USAGE:
    mamba.exe [FLAGS] [OPTIONS]

FLAGS:
    -a, --annotate          Enable type annotation of the output source.
                            Currently still buggy feature.
    -d, --debug             Add line numbers to log statements
    -h, --help              Prints help information
    -l, --level             Print log level
        --no-module-path    Disable the module path in the log statements
        --no-color          Disable colorized output
    -v                      Set level of verbosity
                            - v   : info, error, warning printed to sterr (Default)
                            - vv  : debug messages are printed
                            - vvv : trace messages are printed
    -V, --version           Prints version information

OPTIONS:
    -i, --input <INPUT>      Input file or directory.
                             If file, file taken as input.
                             If directory, recursively search all sub-directories for *.mamba files.
                             If no input given, current directory used as input directory.
    -o, --output <OUTPUT>    Output directory to store Python files.
                             Output directory structure reflects input directory structure.
                             If no output given, 'target' directory created in current directory.

您可以通过输入mamba -help来获取包含上述信息的消息。

👥 贡献

在提交您的第一个问题或拉取请求之前,请花时间阅读我们的贡献指南和我们的行为准则

依赖关系

~6–15MB
~194K SLoC