Skip to content

A4-Tacks/mindustry_logic_bang_lang

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

English README, please click on README-en_US.md

简介

这是语言mindustry_logic_bang的编译器, 编译目标语言是Mindustry游戏中的逻辑语言

逻辑语言在这是指Mindustry 游戏中的一种名为逻辑处理器的建筑中编写与汇编相似的代码并将其导出的形式

对比

  1. 流程控制

    mindustry_logic_bang语言中, 可以使用if elif else while do_while switch等语句完成流程控制


    逻辑语言中, 跳转方式只有jump也就是goto, 又或者设置@counter也就是运行时决定跳转到的位置, 这两种方式的可读性都很差

  2. 代码复用

    mindustry_logic_bang语言中, 我们可以使用const-DExptake结合使用, 它的行为可以类似于宏, 并完成零开销的代码复用.
    这门语言被设计为零开销语言, 你可以零开销的完成很多事, 而不是在编写效率与运行效率之间进行取舍.

    并且可以利用句柄模式匹配条件编译和宏重复等获得极大的灵活度, 还可以配合绑定量泄露和编译期计算完成类似结构体和类型方法的效果, 将类型组合等


    逻辑语言中, 代码复用也不是很强, 如果手动将其封装为一个函数, 则要接受:

    1. 设置返回地址
    2. 跳转至函数头部
    3. 在函数尾部跳转回返回地址

    我们起码需要接受这整整三行的开销, 这在编写小型快速的功能时完全不能接受. 并且更复杂的场景需要传入参数甚至返回值, 这想要做到开销就更大了.

  3. 条件语句

    mindustry_logic_bang语言中, 我们可以使用<= >=等符号进行比较, 并且可以使用&& || !符号进行组织复杂逻辑


    逻辑语言中, 我们如果在游戏内自带的编辑界面, 我们可以选择<= >=等符号.
    但是如果是手动编辑逻辑语言, 我们将会看到lessThanEq greaterThanEq, 这编辑起来很不方便.
    并且组织复杂条件困难, 我们都知道逻辑语言是直接用jump的, 而这个jump是单条件的, 我们需要手动编写短路逻辑来向指定位置各种跳转, 这太恐怖了!
    (因为密密麻麻的jump, 很多复杂逻辑经常被称作"盘丝洞")

  4. 运算

    mindustry_logic_bang语言中, 我们可以使用DExp来将语句嵌套的塞进一行, 可以在一行内完成多个计算.

    且拥有OpExpr这种简单计算方式, 比如print (?a+b*c+log(x));

    产生的中间变量完全由编译器自动生成名字, 当然, 在之后需要使用此中间变量的情况下,您可以手动指定此变量以实现零开销

    如果你手动编写逻辑语言而不是使用内置的编辑器, 那么对于常用运算依旧要使用其序列化名称如add idiv等, 而本语言对于这些常用的运算都分配了运算符号, 可以提升编写体验.


    逻辑语言中, 每一行只能有一个op来进行运算, 这经常会导致很多行的运算, 非常搞心态, 并且还要注意中间变量的复杂关系.

  5. 学习成本

    注意: 学习这门语言首先要对逻辑语言较为熟悉

    这门语言包含的内容并不是很多, 并且为大多数语法提供了一个示例, 按照示例学习可以快速的掌握这门语言.

    并且在examples/std/中, 有着一些编写好的const-DExp, 可以让你知道怎样规范的编写const-DExp.

  6. 特殊语句

    对于一些常用的特殊语句, 如set print, 是被专门处理的

    例如:

    bang语言 逻辑语言
    set a 2; set a 2
    a b = 1 2; set a 1
    set b 2
    print 1 2; print 1
    print 2
    op i i + 1; op add i i 1
    op + i i 1; op add i i 1

    所以不用再编写十几行print来打印了, 可以放到一两行中了.

这是一份示例的代码及编译结果

Bang语言代码:

id, count = 0;

while id < @unitCount {
    lookup unit unit_type id;
    const Bind = (@unit: ubind unit_type;);

    :restart # 用于开始统计该种单位的跳转点

    skip Bind === null {
        # 目前已经绑定了一个非空单位
        first icount = @unit 1;

        while Bind != first {
            # 若头单位死亡, 则重新统计该类单位
            goto :restart (sensor $ first @dead;);
            icount++;
        }
        count += icount; # 将该单位数累加到总单位数

        # 打印每种存在的单位
        print unit_type ": " icount "\n";
    }

    id++; # 推进单位id
}

print "unit total: " count;
printflush message1;

以上代码将被编译为

set id 0
set count id
jump 22 greaterThanEq id @unitCount
lookup unit unit_type id
ubind unit_type
jump 20 strictEqual @unit null
set first @unit
set icount 1
ubind unit_type
jump 15 equal @unit first
sensor __0 first @dead
jump 4 notEqual __0 false
op add icount icount 1
ubind unit_type
jump 10 notEqual @unit first
op add count count icount
print unit_type
print ": "
print icount
print "\n"
op add id id 1
jump 3 lessThan id @unitCount
print "unit total: "
print count
printflush message1

项目构建

构建这个项目将会比较慢, 原因如下:

  1. 使用rustc进行编译, 而它略慢, 相对于gcc clang
  2. 使用了大型语法分析框架lalrpop, 它会生成近六十万行代码, 再叠加上rustc编译更慢

你可以先翻一翻Releases, 看一看有没有已构建的程序, 如果没有或无法使用再尝试自己构建.

构建方法

首先安装rust工具链, 安装方式可以参考 https://www.rust-lang.org/tools/install
请确保你正在使用的工具链是stable版本的.

接下来的构建需要更新索引并从crates-io中获取依赖, 你应该具有合适的网络环境或者配置了镜像源等

将工作目录切换至项目路径(一般就是你git clone下来生成的那个目录)

cargo build --release # 执行这个你可以在target/release下获得编译完成的二进制文件
cargo install --path . # 执行这个你可以在你的shell中直接使用它(假设你已经配置好cargo相关环境)

编辑器支持

为一些编辑器提供了基础的支持

  • Vim: 这是一个活跃在Unix, Linux等平台的编辑器, 虽然相对来说比较小众
    为其配置了基础的语法高亮及折叠, 与缩进规则.
    并且如果你在使用coc-snippets, 或者Ultisnips(未测试) 的话, 你可以享受一些配置的代码片段, 如set 流程控制语法 op iop

  • MT-Manager: 这是一个安卓端的文件管理器, 其中有一个文本编辑器, 可支持自定义高亮, 为它配置了基础的语法高亮.

  • VSCode: 这是一个跨平台的编辑器, 由 westernat 提供了它对Bang语言的语法支持

  • BlocklyEditor: 这是一个图形化代码编辑器框架, 使用此框架实现了一个关于Bang语言的编辑器

    具有中文及英文两个分支

LSP 目前暂无实现, 也没啥必要实现, 逻辑语言这乱的, 这功能也没法用啥

性能

就算你塞几千行代码也基本是瞬间完成, 不用担心什么性能.

报错

报错不怎么友好, 不过报错也比较少, 信息也差不多够找出错误
就是可能使用高级功能时调试起来过于地狱

不过好在, 你使用较为基本的功能并不会遇到那些恐怖的高级错误

如何使用

我们先说明本示例程序的文件名为mindustry_logic_bang_lang, 因为可能由于平台原因或个人进行的重命名带来名称不同, 例如在 Windows 上会有exe后缀.

这个编译器是从输入流中读取输入, 然后输出到输出流(标准输出)或者标准错误, 而我们可以使用shell的重定向功能将文件作为输入流, 并将输出流输出到另一个文件.

以下为一个示例:

mindustry_logic_bang_lang cl < my_source.mdtlbl > out.logic

这个示例中, 我们使用了几乎所有shell都会有的语法, <>.

  • 参数c代表将输入的Bang语言编译为逻辑语言, 然后参数l执行lint做一些检查
  • <后面跟着一个文件, 将这个文件作为程序的标准输入,
  • >后面跟着一个文件, 并将这个文件作为程序标准输出, 也就是标准输出被覆写进这个文件

如果有时需要直观的看到标记展开的形式, 可以将c参数改为Li参数, 将变成逻辑可导入的含标记形式. 就是会丢掉一些跳转优化.

如果你的文件名或者其路径包含空格或特殊字符, 那么你可能需要使用单引号或双引号将其包裹.

其它的编译选项可以不传入任何参数来查看其说明:

mindustry_logic_bang_lang

关于其它编译器的对比

除了 bang 的编译器, 还有不少好用的编译器可以将易于编写的语言编译到逻辑语言, 例如:

一个简单的用于对比的例子

  1. Bang: code-and-compiled
  2. mlogjs: code compiled
  3. mindcode: code 目前暂未编译