Project Icon

gopher-lua

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

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

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

项目侧边栏1项目侧边栏2
推荐项目
Project Cover

豆包MarsCode

豆包 MarsCode 是一款革命性的编程助手,通过AI技术提供代码补全、单测生成、代码解释和智能问答等功能,支持100+编程语言,与主流编辑器无缝集成,显著提升开发效率和代码质量。

Project Cover

AI写歌

Suno AI是一个革命性的AI音乐创作平台,能在短短30秒内帮助用户创作出一首完整的歌曲。无论是寻找创作灵感还是需要快速制作音乐,Suno AI都是音乐爱好者和专业人士的理想选择。

Project Cover

有言AI

有言平台提供一站式AIGC视频创作解决方案,通过智能技术简化视频制作流程。无论是企业宣传还是个人分享,有言都能帮助用户快速、轻松地制作出专业级别的视频内容。

Project Cover

Kimi

Kimi AI助手提供多语言对话支持,能够阅读和理解用户上传的文件内容,解析网页信息,并结合搜索结果为用户提供详尽的答案。无论是日常咨询还是专业问题,Kimi都能以友好、专业的方式提供帮助。

Project Cover

阿里绘蛙

绘蛙是阿里巴巴集团推出的革命性AI电商营销平台。利用尖端人工智能技术,为商家提供一键生成商品图和营销文案的服务,显著提升内容创作效率和营销效果。适用于淘宝、天猫等电商平台,让商品第一时间被种草。

Project Cover

吐司

探索Tensor.Art平台的独特AI模型,免费访问各种图像生成与AI训练工具,从Stable Diffusion等基础模型开始,轻松实现创新图像生成。体验前沿的AI技术,推动个人和企业的创新发展。

Project Cover

SubCat字幕猫

SubCat字幕猫APP是一款创新的视频播放器,它将改变您观看视频的方式!SubCat结合了先进的人工智能技术,为您提供即时视频字幕翻译,无论是本地视频还是网络流媒体,让您轻松享受各种语言的内容。

Project Cover

美间AI

美间AI创意设计平台,利用前沿AI技术,为设计师和营销人员提供一站式设计解决方案。从智能海报到3D效果图,再到文案生成,美间让创意设计更简单、更高效。

Project Cover

AIWritePaper论文写作

AIWritePaper论文写作是一站式AI论文写作辅助工具,简化了选题、文献检索至论文撰写的整个过程。通过简单设定,平台可快速生成高质量论文大纲和全文,配合图表、参考文献等一应俱全,同时提供开题报告和答辩PPT等增值服务,保障数据安全,有效提升写作效率和论文质量。

投诉举报邮箱: service@vectorlightyear.com
@2024 懂AI·鲁ICP备2024100362号-6·鲁公网安备37021002001498号