# 使用 Lua 语言及其脚本实现 Fortran 程序的热重载

`fortran-lua53`🔥仓库 (许可证: ISC，类似BSD-2): [https://github.com/interkosmos/fortran-lua53](https://github.com/interkosmos/fortran-lua53)

`fortran-lua`是 [Philipp](https://github.com/interkosmos) 编写的开源项目，它是 Lua 5.3 的 Fortran 接口。

<div align="center">
<img src="media/luaa.gif" alt="Lua" width="150">
</div>

## Lua 简介

Lua 由 C 语言编程，可以作为单独的 Lua 解释器或 C 语言函数库，是一名优秀小巧的函数式动态语言，其速度是动态语言中 Top 0 级别的，另外，Lua 语言是罕见的由发展中国家设计开发出的著名语言。

它的主要应用场景是:

- 为编译型语言带来灵活性;
- 为编译型语言代码可拓展性，这一点体现在热重载能力。

> 🔰 热重载: 一般情况下，当不具备任何反射特性的编译型语言一旦完成编译，实现部署，中途如果发现 Bug ，则需要重新修改源码，再进行编译部署，且需要中断之前正在运行的含 Bug 的程序。  
> 但是，通过 Lua 脚本，比如 Fortran ，可以借助 Lua 只实现 Lua 脚本的内容，就能一定程度改变程序的后续运行过程，可以一定程度避免中断运行。  
> 这点与配置文件似乎有着异曲同工之妙，但配置文件主要负责程序数据的配置，而 Lua 脚本则更体现出从函数（方法）上改变程序的运行业务，且更灵活，数据配置亦可。  
> 所以，Lua 在游戏开发行业用的较多，可以为上线的游戏快速推送 Lua 补丁，性能又好，体积又小。
> 在科学计算中，如计算流体力学，我们可以通过自定义lua脚本实现网格、粒子的初始化，从内存传递给核心计算程序。

## 演示: 使用 Lua 脚本改变程序的运行效果

我们演示一下在代码编写过程中写错了 Lua 业务，然后在不修改 Fortran 代码、不重新编译代码、不中断程序运行的情况下，实现 Lua 业务修改和正确运行：

```sh
cd workspace  # 切换到你常用的工作区间
fpm new --app lua-demo  # 创建fpm项目
cd lua-demo && code .   # 切换到`lua-demo`文件夹，并使用vs code打开它
```

我们创建了一个`lua-demo`工程，使用vs code打开了它，我们可以在fpm工程的`fpm.toml`文件中添加以下语句，以使用`fortran-lua53`：

```toml
[dependencies]
fortran-lua53 = { git = "https://github.com/interkosmos/fortran-lua53.git" }
```

![错误业务](media/lua_false.png)

上图，我们“不小心”在 Lua 脚本中写了一个错误的`times`函数（粗心的程序员写成了加法函数），可是程序已经运行了，幸亏这个 Bug 不会导致程序崩溃，我们得赶紧修改它，并且将这个补丁发送给用户。

```sh
 5 * 5 =           10
 按回车继续运行..
```

![正确业务](media/lua_true.png)

我们在程序运行的间隙，修改了`times`函数的业务，并且推送给用户运行环境，此后程序业务得到正常合理运行。

```sh
 5 * 5 =           25
 按回车继续运行..
```

我们能看到，Lua 脚本的强大之处，尤其是对强编译型语言的特殊需求，它使得程序变得更灵活，相当于用户业务程序实时自带了一个实时高效的 Lua 解释器。

大多数情况下，这个特性常规用户是不太需要的，但确实为编程提供了一种特色强烈的可能性，跨语言交互和热重载。

## 代码

```fortran
program main
    
    use, intrinsic :: iso_c_binding, only: c_ptr
    use lua
    implicit none
    !> C 指针, 可以指向任何类型的 C 对象
    type(c_ptr) :: l
    integer :: rc
    
    do
        !> 启动 Lua 虚拟机
        l = lual_newstate()
        call lual_openlibs(l)
        rc = lual_dofile(l, "times.lua")
        rc = lua_getglobal(l, "times")
        
        !> 推送参数值
        call lua_pushinteger(l, 5)
        call lua_pushinteger(l, 5)
        rc = lua_pcall(l, 2, 1, 0) ! 调用times函数，推送2个参数，返回1个结果
        
        !> 获取返回值
        rc = lua_tointeger(l, -1) ! -index: 第几个返回值
        call lua_pop(l, 1)        ! 弹出返回值及其数量
        
        print *, "5 * 5 = ", rc
        print *, "按回车继续运行.."
        read (*, *)
        
    end do
    
end program main
```

```lua
-- 整数乘法
function times(a, b)  
    return a + b
end
```
