gopher-lua

gopher-lua

用Go实现的Lua5.1虚拟机和编译器

GopherLua是一个用Go语言实现的Lua5.1虚拟机和编译器。它提供友好的Go API,方便开发者将Lua脚本嵌入Go程序。GopherLua支持协程、自定义类型、模块加载等特性,性能与Python3相当。该项目遵循Lua的设计理念,致力于打造一个具有可扩展语义的脚本语言。

GopherLuaLuaGo虚拟机编译器Github开源项目

GopherLua:Go语言编写的Lua虚拟机和编译器。

===============================================================================

GopherLua是一个用Go语言编写的Lua 5.1(加上Lua 5.2中的goto语句)虚拟机和编译器。GopherLua与Lua有着相同的目标:成为一种具有可扩展语义的脚本语言。它提供了Go语言API,让您可以轻松地将脚本语言嵌入到您的Go宿主程序中。

目录 :depth: 1


设计原则

  • 成为一种具有可扩展语义的脚本语言。
  • 用户友好的Go API
    • 像原始Lua实现中使用的基于栈的API会导致GopherLua的性能提升 (它将减少内存分配和具体类型 <-> 接口转换)。 GopherLua API不是基于栈的API。 GopherLua更注重用户友好性而非性能。

性能如何?

我认为GopherLua虽然不是最快的,但也不算太慢。

在微基准测试中,GopherLua的性能几乎与Python3相当(或略好)。

wiki页面 <https://github.com/yuin/gopher-lua/wiki/Benchmarks>_上有一些基准测试结果。


安装

.. code-block:: bash

go get github.com/yuin/gopher-lua

GopherLua支持Go 1.9及以上版本。


使用方法

GopherLua的API与Lua的使用方式非常相似,但栈仅用于传递参数和接收返回值。

GopherLua支持通道操作。请参阅**"Goroutines"**部分。

导入包。

.. code-block:: go

import ( "github.com/yuin/gopher-lua" )

在虚拟机中运行脚本。

.. code-block:: go

L := lua.NewState() defer L.Close() if err := L.DoString(print("hello")); err != nil { panic(err) }

.. code-block:: go

L := lua.NewState() defer L.Close() if err := L.DoFile("hello.lua"); err != nil { panic(err) }

更多信息请参考Lua参考手册 <http://www.lua.org/manual/5.1/>Go文档 <http://godoc.org/github.com/yuin/gopher-lua>

请注意,Go文档 <http://godoc.org/github.com/yuin/gopher-lua>中未注释的元素等同于Lua参考手册 <http://www.lua.org/manual/5.1/>,但GopherLua使用对象而不是Lua栈索引。

数据模型

GopherLua程序中的所有数据都是LValueLValue是一个接口类型,具有以下方法:

  • String() string
  • Type() LValueType

实现LValue接口的对象有:

================ ========================= ================== ======================= 类型名称 Go类型 Type()值 常量 ================ ========================= ================== ======================= LNilType (常量) LTNil LNil LBool (常量) LTBool LTrue, LFalse LNumber float64 LTNumber - LString string LTString - LFunction 结构体指针 LTFunction - LUserData 结构体指针 LTUserData - LState 结构体指针 LTThread - LTable 结构体指针 LTTable - LChannel chan LValue LTChannel - ================ ========================= ================== =======================

您可以通过Go的方式(类型断言)或使用Type()值来测试对象类型。

.. code-block:: go

lv := L.Get(-1) // 获取栈顶的值 if str, ok := lv.(lua.LString); ok { // lv 是 LString fmt.Println(string(str)) } if lv.Type() != lua.LTString { panic("需要字符串。") }

.. code-block:: go

lv := L.Get(-1) // 获取栈顶的值 if tbl, ok := lv.(*lua.LTable); ok { // lv 是 LTable fmt.Println(L.ObjLen(tbl)) }

注意,LBoolLNumberLString不是指针。

要测试LNilTypeLBool,您必须使用预定义的常量。

.. code-block:: go

lv := L.Get(-1) // 获取栈顶的值

if lv == lua.LTrue { // 正确 }

if bl, ok := lv.(lua.LBool); ok && bool(bl) { // 错误 }

在Lua中,nilfalse都使条件为假。LVIsFalseLVAsBool实现了这个规范。

.. code-block:: go

lv := L.Get(-1) // 获取栈顶的值 if lua.LVIsFalse(lv) { // lv 是 nil 或 false }

if lua.LVAsBool(lv) { // lv 既不是 nil 也不是 false }

基于Go结构体的对象(LFunctionLUserDataLTable)有一些公共方法和字段。您可以出于性能和调试的目的使用这些方法和字段,但有一些限制:

  • 元表不起作用。
  • 没有错误处理。
调用栈和注册表大小

LState的调用栈大小控制脚本中Lua函数的最大调用深度(Go函数调用不计入)。

LState的注册表为调用函数(包括Lua和Go函数)以及表达式中的临时变量实现了栈存储。它的存储需求会随着调用栈的使用和代码复杂度的增加而增加。

注册表和调用栈都可以设置为固定大小或自动调整大小。

当您在一个进程中实例化了大量LState时,值得花时间调整注册表和调用栈的选项。

+++++++++ 注册表 +++++++++

注册表可以在每个LState的基础上配置初始大小、最大大小和步进大小。这将允许注册表根据需要增长。增长后不会再缩小。

.. code-block:: go

L := lua.NewState(lua.Options{
   RegistrySize: 1024 * 20,         // 这是注册表的初始大小
   RegistryMaxSize: 1024 * 80,      // 这是注册表可以增长到的最大大小。如果设置为`0`(默认值),则注册表不会自动增长
   RegistryGrowStep: 32,            // 这是每次注册表空间不足时增加的步长。默认值为`32`。
})

defer L.Close()

对于给定的脚本,如果注册表太小,最终会导致崩溃。如果注册表太大,会浪费内存(如果实例化了多个LState,这可能会很显著)。 自动增长的注册表在调整大小时会稍微影响性能,但不会影响其他方面的性能。

+++++++++ 调用栈 +++++++++ 调用栈可以以两种不同的模式运行:固定大小或自动调整大小。

固定大小的调用栈具有最高的性能,并且内存开销固定。

自动调整大小的调用栈会根据需求分配和释放调用栈页,确保在任何时候都使用最少的内存。缺点是每次分配新的调用帧页时都会产生一些性能影响。

默认情况下,LState 会以 8 个为一页分配和释放调用栈帧,因此不会在每次函数调用时都产生分配开销。对于大多数用例来说,自动调整大小的调用栈的性能影响可能微不足道。

.. code-block:: go

L := lua.NewState(lua.Options{
    CallStackSize: 120,                 // 这是该 LState 的最大调用栈大小
    MinimizeStackMemory: true,          // 如果未指定则默认为 `false`。如果设置,调用栈将根据需要自动增长和收缩,最大不超过 `CallStackSize`。如果未设置,调用栈将固定为 `CallStackSize`。
})

defer L.Close()

++++++++++++++++ 选项默认值 ++++++++++++++++

上面的例子展示了如何为每个 LState 自定义调用栈和注册表大小。你也可以通过修改 lua.RegistrySize、lua.RegistryGrowStep 和 lua.CallStackSize 的值来调整未指定选项时的一些默认值。

由 *LState#NewThread() 创建的 LState 对象会继承父 LState 对象的调用栈和注册表大小。

其他 lua.NewState 选项
  • Options.SkipOpenLibs bool(默认 false)
    • 默认情况下,GopherLua 在创建新的 LState 时会打开所有内置库。
    • 你可以通过将此设置为 true 来跳过此行为。
    • 使用各种 OpenXXX(L *LState) int 函数,你可以只打开所需的库,下面有一个示例。
  • Options.IncludeGoStackTrace bool(默认 false)
    • 默认情况下,GopherLua 在发生 panic 时不显示 Go 堆栈跟踪。
    • 你可以通过将此设置为 true 来获取 Go 堆栈跟踪。
API

有关更多信息,请参阅 Lua 参考手册和 Go 文档(LState 方法)。

+++++++++++++++++++++++++++++++++++++++++ 从 Lua 调用 Go +++++++++++++++++++++++++++++++++++++++++

.. code-block:: go

func Double(L lua.LState) int { lv := L.ToInt(1) / 获取参数 / L.Push(lua.LNumber(lv * 2)) / 推送结果 / return 1 / 结果数量 */ }

func main() { L := lua.NewState() defer L.Close() L.SetGlobal("double", L.NewFunction(Double)) /* 原始的 lua_setglobal 使用栈... */ }

.. code-block:: lua

print(double(20)) -- > "40"

任何在 GopherLua 中注册的函数都是一个 lua.LGFunction,定义在 value.go 中

.. code-block:: go

type LGFunction func(*LState) int

使用协程。

.. code-block:: go

co, _ := L.NewThread() /* 创建一个新线程 */ fn := L.GetGlobal("coro").(lua.LFunction) / 从 lua 获取函数 */ for { st, err, values := L.Resume(co, fn) if st == lua.ResumeError { fmt.Println("yield break(error)") fmt.Println(err.Error()) break }

   for i, lv := range values {
       fmt.Printf("%v : %v\n", i, lv)
   }

   if st == lua.ResumeOK {
       fmt.Println("yield break(ok)")
       break
   }

}

+++++++++++++++++++++++++++++++++++++++++ 打开内置模块的子集 +++++++++++++++++++++++++++++++++++++++++

以下演示了如何在 Lua 中打开内置模块的子集,例如,为了避免启用具有访问本地文件或系统调用权限的模块。

main.go

.. code-block:: go

func main() {
    L := lua.NewState(lua.Options{SkipOpenLibs: true})
    defer L.Close()
    for _, pair := range []struct {
        n string
        f lua.LGFunction
    }{
        {lua.LoadLibName, lua.OpenPackage}, // 必须是第一个
        {lua.BaseLibName, lua.OpenBase},
        {lua.TabLibName, lua.OpenTable},
    } {
        if err := L.CallByParam(lua.P{
            Fn:      L.NewFunction(pair.f),
            NRet:    0,
            Protect: true,
        }, lua.LString(pair.n)); err != nil {
            panic(err)
        }
    }
    if err := L.DoFile("main.lua"); err != nil {
        panic(err)
    }
}

+++++++++++++++++++++++++++++++++++++++++ 用 Go 创建模块 +++++++++++++++++++++++++++++++++++++++++

mymodule.go

.. code-block:: go

package mymodule

import (
    "github.com/yuin/gopher-lua"
)

func Loader(L *lua.LState) int {
    // 向表中注册函数
    mod := L.SetFuncs(L.NewTable(), exports)
    // 注册其他内容
    L.SetField(mod, "name", lua.LString("value"))

    // 返回模块
    L.Push(mod)
    return 1
}

var exports = map[string]lua.LGFunction{
    "myfunc": myfunc,
}

func myfunc(L *lua.LState) int {
    return 0
}

mymain.go

.. code-block:: go

package main

import (
    "./mymodule"
    "github.com/yuin/gopher-lua"
)

func main() {
    L := lua.NewState()
    defer L.Close()
    L.PreloadModule("mymodule", mymodule.Loader)
    if err := L.DoFile("main.lua"); err != nil {
        panic(err)
    }
}

main.lua

.. code-block:: lua

local m = require("mymodule")
m.myfunc()
print(m.name)

+++++++++++++++++++++++++++++++++++++++++ 从 Go 调用 Lua +++++++++++++++++++++++++++++++++++++++++

.. code-block:: go

L := lua.NewState() defer L.Close() if err := L.DoFile("double.lua"); err != nil { panic(err) } if err := L.CallByParam(lua.P{ Fn: L.GetGlobal("double"), NRet: 1, Protect: true, }, lua.LNumber(10)); err != nil { panic(err) } ret := L.Get(-1) // 返回值 L.Pop(1) // 移除接收到的值

如果 Protect 为 false,GopherLua 将会 panic 而不是返回 error 值。

+++++++++++++++++++++++++++++++++++++++++ 用户定义类型 +++++++++++++++++++++++++++++++++++++++++ 你可以用 Go 编写新类型来扩展 GopherLua。 为此提供了 LUserData。

.. code-block:: go

type Person struct {
    Name string
}

const luaPersonTypeName = "person"

// 向给定的 L 注册我的 person 类型。
func registerPersonType(L *lua.LState) {
    mt := L.NewTypeMetatable(luaPersonTypeName)
    L.SetGlobal("person", mt)
    // 静态属性
    L.SetField(mt, "new", L.NewFunction(newPerson))
    // 方法
    L.SetField(mt, "__index", L.SetFuncs(L.NewTable(), personMethods))
}

// 构造函数
func newPerson(L *lua.LState) int {
    person := &Person{L.CheckString(1)}
    ud := L.NewUserData()
    ud.Value = person
    L.SetMetatable(ud, L.GetTypeMetatable(luaPersonTypeName))
    L.Push(ud)
    return 1
}

// 检查第一个 lua 参数是否为带有 *Person 的 *LUserData,并返回这个 *Person。
func checkPerson(L *lua.LState) *Person {
    ud := L.CheckUserData(1)
    if v, ok := ud.Value.(*Person); ok {
        return v
    }
    L.ArgError(1, "person expected")
    return nil
}

var personMethods = map[string]lua.LGFunction{
    "name": personGetSetName,
}

// Person#Name 的 getter 和 setter
func personGetSetName(L *lua.LState) int {
    p := checkPerson(L)
    if L.GetTop() == 2 {
        p.Name = L.CheckString(2)
        return 0
    }
    L.Push(lua.LString(p.Name))
    return 1
}

func main() { L := lua.NewState() defer L.Close() registerPersonType(L) if err := L.DoString( p = person.new("Steeve") print(p:name()) -- "Steeve" p:name("Alice") print(p:name()) -- "Alice" ); err != nil { panic(err) } }

+++++++++++++++++++++++++++++++++++++++++ 终止运行中的LState +++++++++++++++++++++++++++++++++++++++++ GopherLua支持"Go并发模式:上下文"。

.. code-block:: go

L := lua.NewState()
defer L.Close()
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
// 将上下文设置到我们的LState
L.SetContext(ctx)
err := L.DoString(`
  local clock = os.clock
  function sleep(n)  -- 秒
    local t0 = clock()
    while clock() - t0 <= n do end
  end
  sleep(3)
`)
// err.Error()包含"context deadline exceeded"

使用协程

.. code-block:: go

L := lua.NewState()
defer L.Close()
ctx, cancel := context.WithCancel(context.Background())
L.SetContext(ctx)
defer cancel()
L.DoString(`
    function coro()
      local i = 0
      while true do
        coroutine.yield(i)
        i = i+1
      end
      return i
    end
`)
co, cocancel := L.NewThread()
defer cocancel()
fn := L.GetGlobal("coro").(*LFunction)

_, err, values := L.Resume(co, fn) // err为nil

cancel() // 取消父上下文

_, err, values = L.Resume(co, fn) // err不为nil:子上下文被取消

注意使用上下文会导致性能下降。

.. code-block::

time ./glua-with-context.exe fib.lua
9227465
0.01s user 0.11s system 1% cpu 7.505 total

time ./glua-without-context.exe fib.lua
9227465
0.01s user 0.01s system 0% cpu 5.306 total

+++++++++++++++++++++++++++++++++++++++++ 在LState之间共享Lua字节码 +++++++++++++++++++++++++++++++++++++++++ 调用DoFile将加载Lua脚本,将其编译为字节码并在LState中运行字节码。

如果你有多个需要运行相同脚本的LState,你可以在它们之间共享字节码,这将节省内存。 共享字节码是安全的,因为它是只读的,不能被Lua脚本修改。

.. code-block:: go

// CompileLua从磁盘读取传入的lua文件并编译它。
func CompileLua(filePath string) (*lua.FunctionProto, error) {
    file, err := os.Open(filePath)
    defer file.Close()
    if err != nil {
        return nil, err
    }
    reader := bufio.NewReader(file)
    chunk, err := parse.Parse(reader, filePath)
    if err != nil {
        return nil, err
    }
    proto, err := lua.Compile(chunk, filePath)
    if err != nil {
        return nil, err
    }
    return proto, nil
}

// DoCompiledFile接受CompileLua返回的FunctionProto,并在LState中运行它。
// 这相当于在LState上用原始源文件调用DoFile。
func DoCompiledFile(L *lua.LState, proto *lua.FunctionProto) error {
    lfunc := L.NewFunctionFromProto(proto)
    L.Push(lfunc)
    return L.PCall(0, lua.MultRet, nil)
}

// 示例展示了如何在多个VM之间共享Lua脚本的编译字节码。
func Example() {
    codeToShare := CompileLua("mylua.lua")
    a := lua.NewState()
    b := lua.NewState()
    c := lua.NewState()
    DoCompiledFile(a, codeToShare)
    DoCompiledFile(b, codeToShare)
    DoCompiledFile(c, codeToShare)
}

+++++++++++++++++++++++++++++++++++++++++ Goroutines +++++++++++++++++++++++++++++++++++++++++ LState不是goroutine安全的。建议每个goroutine使用一个LState,并通过使用通道在goroutine之间通信。

在GopherLua中,通道由channel对象表示。channel表提供了执行通道操作的函数。

由于内部包含非goroutine安全的对象,某些对象不能通过通道发送。

  • 线程(状态)
  • 函数
  • 用户数据
  • 带有元表的表

不能从Go API将这些对象发送到通道。

.. code-block:: go

func receiver(ch, quit chan lua.LValue) {
    L := lua.NewState()
    defer L.Close()
    L.SetGlobal("ch", lua.LChannel(ch))
    L.SetGlobal("quit", lua.LChannel(quit))
    if err := L.DoString(`
    local exit = false
    while not exit do
      channel.select(
        {"|<-", ch, function(ok, v)
          if not ok then
            print("channel closed")
            exit = true
          else
            print("received:", v)
          end
        end},
        {"|<-", quit, function(ok, v)
            print("quit")
            exit = true
        end}
      )
    end
  `); err != nil {
        panic(err)
    }
}

func sender(ch, quit chan lua.LValue) {
    L := lua.NewState()
    defer L.Close()
    L.SetGlobal("ch", lua.LChannel(ch))
    L.SetGlobal("quit", lua.LChannel(quit))
    if err := L.DoString(`
    ch:send("1")
    ch:send("2")
  `); err != nil {
        panic(err)
    }
    ch <- lua.LString("3")
    quit <- lua.LTrue
}

func main() {
    ch := make(chan lua.LValue)
    quit := make(chan lua.LValue)
    go receiver(ch, quit)
    go sender(ch, quit)
    time.Sleep(3 * time.Second)
}

''''''''''''''' Go API '''''''''''''''

可以使用ToChannelCheckChannelOptChannel

更多信息请参考 Go doc(LState方法) <http://godoc.org/github.com/yuin/gopher-lua>_。

''''''''''''''' Lua API '''''''''''''''

  • channel.make([buf:int]) -> ch:channel

    • 创建一个缓冲区大小为buf的新通道。默认情况下,buf为0。
  • channel.select(case:table [, case:table, case:table ...]) -> {index:int, recv:any, ok}

    • 与Go中的select语句相同。它返回所选case的索引,如果该case是接收操作,则返回接收到的值和一个布尔值,指示通道是否已关闭。
    • case是一个如下所述的表。
      • 接收:{"|<-", ch:channel [, handler:func(ok, data:any)]}
      • 发送:{"<-|", ch:channel, data:any [, handler:func(data:any)]}
      • 默认:{"default" [, handler:func()]}

channel.select示例:

.. code-block:: lua

local idx, recv, ok = channel.select(
  {"|<-", ch1},
  {"|<-", ch2}
)
if not ok then
    print("closed")
elseif idx == 1 then -- 从ch1接收
    print(recv)
elseif idx == 2 then -- 从ch2接收
    print(recv)
end

.. code-block:: lua

channel.select(
  {"|<-", ch1, function(ok, data)
    print(ok, data)
  end},
  {"<-|", ch2, "value", function(data)
    print(data)
  end},
  {"default", function()
    print("default action")
  end}
)
  • channel:send(data:any)
    • 通过通道发送data
  • channel:receive() -> ok:bool, data:any
    • 通过通道接收数据。
  • channel:close()
    • 关闭通道。

'''''''''''''''''''''''''''''' LState池模式 '''''''''''''''''''''''''''''' 要创建每个线程的LState实例,你可以使用类似sync.Pool的机制。

.. code-block:: go

type lStatePool struct {
    m     sync.Mutex
    saved []*lua.LState
}

func (pl *lStatePool) Get() *lua.LState { pl.m.Lock() defer pl.m.Unlock() n := len(pl.saved) if n == 0 { return pl.New() } x := pl.saved[n-1] pl.saved = pl.saved[0 : n-1] return x }

func (pl *lStatePool) New() *lua.LState { L := lua.NewState() // 在此处设置L // 加载脚本,设置全局变量,共享通道等... return L }

func (pl *lStatePool) Put(L *lua.LState) { pl.m.Lock() defer pl.m.Unlock() pl.saved = append(pl.saved, L) }

func (pl *lStatePool) Shutdown() { for _, L := range pl.saved { L.Close() } }

// 全局LState池 var luaPool = &lStatePool{ saved: make([]*lua.LState, 0, 4), }

现在,你可以从 luaPool 中获取每个线程的LState对象。

.. code-block:: go

func MyWorker() {
   L := luaPool.Get()
   defer luaPool.Put(L)
   /* 你的代码在这里 */
}

func main() {
    defer luaPool.Shutdown()
    go MyWorker()
    go MyWorker()
    /* 等等... */
}

Lua和GopherLua之间的差异

协程
  • GopherLua支持通道操作。
    • GopherLua有一个名为 channel 的类型。
    • channel 表提供了执行通道操作的函数。
不支持的函数
  • string.dump
  • os.setlocale
  • lua_Debug.namewhat
  • package.loadlib
  • 调试钩子
其他注意事项
  • collectgarbage 不接受任何参数,会为整个Go程序运行垃圾收集器。
  • file:setvbuf 不支持行缓冲。
  • 不支持夏令时。
  • GopherLua有一个设置环境变量的函数:os.setenv(name, value)
  • GopherLua支持Lua5.2中的 goto::label:: 语句。
    • goto 是关键字,不是有效的变量名。

独立解释器

Lua有一个名为 lua 的解释器。GopherLua有一个名为 glua 的解释器。

.. code-block:: bash

go get github.com/yuin/gopher-lua/cmd/glua

glua 具有与 lua 相同的选项。


如何贡献

请参阅 贡献者指南 <https://github.com/yuin/gopher-lua/tree/master/.github/CONTRIBUTING.md>_ 。


GopherLua的库

  • gopher-luar <https://github.com/layeh/gopher-luar>_ :简化了与gopher-lua之间的数据传递
  • gluamapper <https://github.com/yuin/gluamapper>_ :将Lua表映射到Go结构体
  • gluare <https://github.com/yuin/gluare>_ :gopher-lua的正则表达式
  • gluahttp <https://github.com/cjoudrey/gluahttp>_ :gopher-lua的HTTP请求模块
  • gopher-json <https://github.com/layeh/gopher-json>_ :gopher-lua的简单JSON编码器/解码器
  • gluayaml <https://github.com/kohkimakimoto/gluayaml>_ :gopher-lua的Yaml解析器
  • glua-lfs <https://github.com/layeh/gopher-lfs>_ :部分实现了gopher-lua的luafilesystem模块
  • gluaurl <https://github.com/cjoudrey/gluaurl>_ :gopher-lua的URL解析器/构建器模块
  • gluahttpscrape <https://github.com/felipejfc/gluahttpscrape>_ :gopher-lua的简单HTML抓取模块
  • gluaxmlpath <https://github.com/ailncode/gluaxmlpath>_ :gopher-lua的xmlpath模块
  • gmoonscript <https://github.com/rucuriousyet/gmoonscript>_ :Gopher Lua VM的Moonscript编译器
  • loguago <https://github.com/rucuriousyet/loguago>_ :Gopher-Lua的Zerolog封装器
  • gluacrypto <https://github.com/tengattack/gluacrypto>_ :GopherLua VM的原生Go加密库实现
  • gluasql <https://github.com/tengattack/gluasql>_ :GopherLua VM的原生Go SQL客户端实现
  • purr <https://github.com/leyafo/purr>_ :HTTP模拟测试工具
  • vadv/gopher-lua-libs <https://github.com/vadv/gopher-lua-libs>_ :GopherLua VM的一些有用库
  • gluasocket <https://gitlab.com/megalithic-llc/gluasocket>_ :GopherLua VM的原生Go LuaSocket实现
  • glua-async <https://github.com/CuberL/glua-async>_ :gopher-lua的async/await实现
  • gopherlua-debugger <https://github.com/edolphin-ydf/gopherlua-debugger>_ :gopher-lua的调试器
  • gluamahonia <https://github.com/super1207/gluamahonia>_ :gopher-lua的编码转换器

捐赠

BTC: 1NEDSyUmo4SMTDP83JJQSWi1MvQUGGNMZB


许可证

MIT


作者

Yusuke Inuzuka

编辑推荐精选

Keevx

Keevx

AI数字人视频创作平台

Keevx 一款开箱即用的AI数字人视频创作平台,广泛适用于电商广告、企业培训与社媒宣传,让全球企业与个人创作者无需拍摄剪辑,就能快速生成多语言、高质量的专业视频。

即梦AI

即梦AI

一站式AI创作平台

提供 AI 驱动的图片、视频生成及数字人等功能,助力创意创作

扣子-AI办公

扣子-AI办公

AI办公助手,复杂任务高效处理

AI办公助手,复杂任务高效处理。办公效率低?扣子空间AI助手支持播客生成、PPT制作、网页开发及报告写作,覆盖科研、商业、舆情等领域的专家Agent 7x24小时响应,生活工作无缝切换,提升50%效率!

TRAE编程

TRAE编程

AI辅助编程,代码自动修复

Trae是一种自适应的集成开发环境(IDE),通过自动化和多元协作改变开发流程。利用Trae,团队能够更快速、精确地编写和部署代码,从而提高编程效率和项目交付速度。Trae具备上下文感知和代码自动完成功能,是提升开发效率的理想工具。

AI工具TraeAI IDE协作生产力转型热门
蛙蛙写作

蛙蛙写作

AI小说写作助手,一站式润色、改写、扩写

蛙蛙写作—国内先进的AI写作平台,涵盖小说、学术、社交媒体等多场景。提供续写、改写、润色等功能,助力创作者高效优化写作流程。界面简洁,功能全面,适合各类写作者提升内容品质和工作效率。

AI辅助写作AI工具蛙蛙写作AI写作工具学术助手办公助手营销助手AI助手
问小白

问小白

全能AI智能助手,随时解答生活与工作的多样问题

问小白,由元石科技研发的AI智能助手,快速准确地解答各种生活和工作问题,包括但不限于搜索、规划和社交互动,帮助用户在日常生活中提高效率,轻松管理个人事务。

热门AI助手AI对话AI工具聊天机器人
Transly

Transly

实时语音翻译/同声传译工具

Transly是一个多场景的AI大语言模型驱动的同声传译、专业翻译助手,它拥有超精准的音频识别翻译能力,几乎零延迟的使用体验和支持多国语言可以让你带它走遍全球,无论你是留学生、商务人士、韩剧美剧爱好者,还是出国游玩、多国会议、跨国追星等等,都可以满足你所有需要同传的场景需求,线上线下通用,扫除语言障碍,让全世界的语言交流不再有国界。

讯飞智文

讯飞智文

一键生成PPT和Word,让学习生活更轻松

讯飞智文是一个利用 AI 技术的项目,能够帮助用户生成 PPT 以及各类文档。无论是商业领域的市场分析报告、年度目标制定,还是学生群体的职业生涯规划、实习避坑指南,亦或是活动策划、旅游攻略等内容,它都能提供支持,帮助用户精准表达,轻松呈现各种信息。

AI办公办公工具AI工具讯飞智文AI在线生成PPTAI撰写助手多语种文档生成AI自动配图热门
讯飞星火

讯飞星火

深度推理能力全新升级,全面对标OpenAI o1

科大讯飞的星火大模型,支持语言理解、知识问答和文本创作等多功能,适用于多种文件和业务场景,提升办公和日常生活的效率。讯飞星火是一个提供丰富智能服务的平台,涵盖科技资讯、图像创作、写作辅助、编程解答、科研文献解读等功能,能为不同需求的用户提供便捷高效的帮助,助力用户轻松获取信息、解决问题,满足多样化使用场景。

热门AI开发模型训练AI工具讯飞星火大模型智能问答内容创作多语种支持智慧生活
Spark-TTS

Spark-TTS

一种基于大语言模型的高效单流解耦语音令牌文本到语音合成模型

Spark-TTS 是一个基于 PyTorch 的开源文本到语音合成项目,由多个知名机构联合参与。该项目提供了高效的 LLM(大语言模型)驱动的语音合成方案,支持语音克隆和语音创建功能,可通过命令行界面(CLI)和 Web UI 两种方式使用。用户可以根据需求调整语音的性别、音高、速度等参数,生成高质量的语音。该项目适用于多种场景,如有声读物制作、智能语音助手开发等。

下拉加载更多