lkx语言的总体设计已经发布到github上 (https://github.com/lichuan/lkx)

Lkx language

Lkx is a new strongly typed scripting language, simpler and faster than lua. It can easily interact with c/c++ , the name Lkx comes from my daughter's name (Li Kaixin), so this language is also a gift for my daughter.

Design Principles

  • simple and clear
  • variable typed
  • hot reloading
  • garbage collection
  • user defined structure
  • shared function
  • shared variable
  • faster than Lua
  • c-like syntax
  • strictly consistent
  • convention-based

Lkx overview

Variable Type

supported type description
bool boolean value, true or false
double double precision floating point
string sequence of characters
int8, int16, int32, int64 signed int of 8~64 bits
uint8, uint16, uint32, uint64 unsigned int of 8~64 bits
int8, int16, int32, int64 array of int8~64
uint8, uint16, uint32, uint64 array of uint8~64
bool, double, string array of bool, double, string
{uint8}, {uint16}, {uint32}, {uint64} unordered set of uint8~64
{m uint8}, {m uint16}, {m uint32}, {m uint64} unordered multi set of uint8~64
{o uint8}, {o uint16}, {o uint32}, {o uint64} orderded set of uint8~64
{mo uint8}, {mo uint16}, {mo uint32}, {mo uint64} ordered multi set of uint8~64
{string}, {m string} unordered set of string, unordered multi set of string
{o string}, {mo string} ordered set of string, ordered multi set of string
{uint32:string} unordered map, key type: uint32, value type: string
{string:string} unordered map, key type: string, value type: string
{m string:uint16} unordered multi map
{o uint8:uint16} ordered map
{mo string:uint32} ordered multi map, value type: array of uint32

Module unit

In Lkx, each source file is a module, and each file has a consistent code layout, the source file is logically divided into several parts, each part must be separated by a blank line.

The 1st part is the import statement, which imports shared variables and functions from other files:

go 复制代码
import "family/lib/utils.lkx"
import "family/sister/info.lkx"

The 2nd part is the declaration and initialization of variables, the shared variable must be declared before the local variable:

c 复制代码
shared string brother_name = "bob"
uint32 brother_height = 170
uint32 brother_weight = 55
uint32 brother_age = 31

The 3rd part is the hot reloading hook function, which is used to control whether the variables declared in the current module need to be reinitialized during hot reloading:

c 复制代码
internal void __lkx__hot_reloading_hook__()
{  
}

The 4th part is the definition of the function, shared functions must be defined before local functions, and each function must be separated by a blank line:

c 复制代码
shared bool brother_is_giant()
{
  return is_a_giant(brother_height, brother_weight)
}

shared void brother_grow_up()
{
  brother_height += 10
  brother_weight += 20
}

bool brother_older_than_sister()
{
  return brother_age >= sister_age
}

The source code for this sample file (path: "family/brother/info.lkx"):

go 复制代码
import "family/lib/utils.lkx"
import "family/sister/info.lkx"

shared string brother_name = "bob"
uint32 brother_height = 170
uint32 brother_weight = 55
uint32 brother_age = 31

internal void __lkx__hot_reloading_hook__()
{  
}

shared bool brother_is_tall()
{
  if(brother_height < TALL_CM)
  {
    return false
  }
  
  return true
}

shared bool is_older_brother()
{
  return brother_older_than_sister()
}

shared bool brother_is_giant()
{
  return is_a_giant(brother_height, brother_weight)
}

shared void brother_grow_up()
{
  brother_height += 10
  brother_weight += 20
}

bool brother_older_than_sister()
{
  return brother_age >= sister_age
}

Hot reloading

Lkx code is usually called by a c/c++ host program. After we have developed a project and run it for some time, we may need to modify the variables and functions in the script to meet some new requirements. In this case, Lkx can reload the modified script code without restarting the c/c++ host program.

Before the hot reloading of Lkx script, each variable in the script has been given the corresponding value by many executed functions. During hot reloading, we need to decide which variables should remain unchanged and which variables must be reinitialized based on business logic. Fortunately, Lkx provides an internal hot reloading hook function: __lkx_hot_reloading_hook__() , In this function, you can specify which variables should remain unchanged during the hot reloading process, as follows (path: "family/lib/utils.lkx"):

c 复制代码
shared uint32 TALL_CM = 190
shared uint32 FAT_KG = 90
uint32 GIANT_CM = 300
uint32 GIANT_KG = 500
uint32 call_giant_count = 0

internal void __lkx__hot_reloading_hook__()
{
  call_giant_count = __LKX_REMAIN_UNCHANGED__
}

shared bool is_a_giant(uint32 height, uint32 weight)
{
  if(height < GIANT_CM)
  {
    return false
  }

  if(weight < GIANT_KG)
  {
    return false
  }

  ++call_giant_count
  return true
}

The call_giant_count variable is assigned the special value __LKX_REMAIN_UNCHANGED__ to tell the interpreter that this variable needs to remain unchanged during hot reloading.

User structure

In order to keep the simplicity of Lkx language, the syntax for defining a structure class with data and functions is not supported in script code, but you can define it in c/c++ code and then export it to the script.

In Lkx's c/c++ api, there are two types of struct: inner struct and outer struct. There are two main differences between them:

  1. The inner struct is usually created by the Lkx script, and its attribute members can be directly accessed; the outer struct is usually created by the c/c++ host, and its members can only be accessed through member functions in the script.

  2. The inner struct is created by the script, so its GC should also be processed by the Lkx interpreter, while the outer struct is created by the c/c++ host, so the interpreter usually does not need to process its GC.

In addition, you can also assign an outer struct to a data member of an inner struct, the following example shows how to export the inner and outer struct to the Lkx script:

c 复制代码
typedef int (*lkx_Func)(lkx_Hub*);

typedef struct lkx_Reg_Func {
  const char *name;
  lkx_Func func;
} lkx_Reg_Func;

typedef struct lkx_Reg_Attr {
  const char *name;
  const char *type;
} lkx_Reg_Attr;

int main()
{
  lkx_Hub *lkx_hub = lkx_new_hub();
  lkx_init(lkx_hub, "./deploy/script/family/hub.lkx");
  lkx_load(lkx_hub);

  //outer struct CUser
  lkx_Reg_Func cuserfuncs[] =
  {
    {"get_id", lkx__CUser__get_id},
    {"get_name", lkx__CUser__get_name},
    {"send_msg", lkx__CUser__send_msg},
    {"save_data", lkx__CUser__save_data},
    {NULL, NULL}
  };

  //inner struct User
  lkx_Reg_Attr userattrs[] = {
    {"level", "uin32"},
    {"cuser", "CUser"},
    {"country", "string"},
    {"money", "uint32"},
    {"children", "[User]"},
    {NULL, NULL}
  };

  //outer struct CUser
  lkx_reg_outer_struct(lkx_hub, "CUser", cuserfuncs);
  lkx_reg_struct_metafunc(lkx_hub, "CUser", "__hash__", lkx__CUser____hash__, 1);
  lkx_reg_struct_metafunc(lkx_hub, "CUser", "__m_hash__", lkx__CUser____m_hash__, 2);
  lkx_reg_struct_metafunc(lkx_hub, "CUser", "__o_compare__", lkx__CUser____o_compare__, 0);
  lkx_reg_struct_metafunc(lkx_hub, "CUser", "__mo_compare__", lkx__CUser____mo_compare__, 0);

  //inner struct User
  lkx_reg_inner_struct(lkx_hub, "User", userattrs);
  lkx_reg_struct_metafunc(lkx_hub, "User", "__hash__", lkx__User____hash__, 1);
  lkx_reg_struct_metafunc(lkx_hub, "User", "__m_hash__", lkx__User____m_hash__, 2);
  lkx_reg_struct_metafunc(lkx_hub, "User", "__o_compare__", lkx__User____o_compare__, 0);
  lkx_reg_struct_metafunc(lkx_hub, "User", "__mo_compare__", lkx__User____mo_compare__, 0);

  //other code in main......
  ......
}

Convention

Precedence of convention over complex syntax is a design principle of the Lkx language.

In Lkx, the file paths in the import statement are all prefixed with a unified project related name, which can reduce name conflicts throughout the entire project, as follows:

go 复制代码
import "family/father/info.lkx"
import "family/mother/info.lkx"
import "family/brother/info.lkx"

Because shared variables and functions can be accessed in all script files, it is best to add meaningful prefix characters when naming them. For example, in the following lkx script file (path: "family/brother/info.lkx") , variables are prefixed with "brother_":

go 复制代码
import "family/lib/utils.lkx"
import "family/sister/info.lkx"

shared string brother_name = "bob"
uint32 brother_height = 170
uint32 brother_weight = 55
uint32 brother_age = 31

Lkx does not support the const keyword, if you want to define a constant, you can change the name of the variable to uppercase. In Lkx script, the uppercase variable name represents a constant. This is also the design philosophy of Lkx based on convention:

c 复制代码
shared uint32 TALL_CM = 190
shared uint32 FAT_KG = 90
uint32 GIANT_CM = 300
uint32 GIANT_KG = 500
uint32 call_giant_count = 0

Variable initialization

All variables declared in the Lkx file will be initialized to their corresponding values by the interpreter:

  • numeric variable is initialized to 0
  • bool variable is initialized to false
  • array is initialized to []
  • set is initialized to {}
  • map is initialized to {:}
  • struct variable is initialized to null

Hub file

When the Lkx interpreter parses the import statement, it will find the corresponding source file according to the __lkx_import_path_from_c_workdir__ set in the hub file. In addition, the hub file must import all other source files of the project, because the first file read by the interpreter is the hub file, and then parse other imported files in turn.

go 复制代码
[string] __lkx_import_path_from_c_workdir__ = 
[
  "./deploy/script/",
  "./deploy/script/3rdlib/",
  "./deploy/goolge/lkxlib/",
  "./deploy/tesla/lkxlib/",
  "./deploy/apple/lkxlib/"
]

import "family/father/info.lkx"
import "family/mother/info.lkx"
import "family/brother/info.lkx"

Timer

As a scripting language, Lkx itself does not provide any timer functions, but you can easily implement it. As shown below, you can periodically call a script function in the main loop of c/c++to implement a timer:

c 复制代码
while(true)
{
  sleep(1);
  lkx_call(lkx_hub, "timer_func_per_second");
}

Multithreading

lkx_Hub is the execution environment in Lkx, and each script function call runs inside a specific lkx_Hub. Each lkx_hub is an independent sandbox, so you can run different lkx_Hubs on different threads to get multithreading support. However, multithreaded programming is not easy and requires you to be more careful.

Lkx vs Lua

  • simpler than Lua
  • more consistent than Lua
  • faster than Lua
  • better looking in Lkx than Lua
  • without Lua's ugly if end block
  • Lua is not strongly typed, so it is not suitable for large programs

Implementation

  • cimpl
    c impl of Lkx, main part of my work.

  • csimpl
    c# impl of Lkx, need your contribution.

  • javaimpl
    java impl of Lkx, need your contribution.

  • pyimpl
    python impl of Lkx, need your contribution.

  • goimpl
    go impl of Lkx, need your contribution.

Contact

If you're interested in Lkx language, join the telegram group: https://t.me/lkx_language

相关推荐
鹏毓网络科技21 小时前
Cursor Rules 文件配置实战:3 个隐藏参数让我每月少写 40% 样板代码
前端·github
嘻嘻仙人2 天前
Ubuntu中 git上传自己的项目和二次上传一般流程
git·github
白鲸开源2 天前
Apache SeaTunnel Zeta Engine 的 Basic Auth 是怎么工作的?
java·vue.js·github
白鲸开源2 天前
一文读懂DolphinScheduler插件机制:如何轻松扩展任务类型与数据源
java·架构·github
徐小夕3 天前
万字拆解 JitWord:企业级实时协同文档底层架构 + 大模型 AI 融合完整实践
前端·vue.js·github
码流怪侠3 天前
【GitHub】Ponytail:给 AI 编码代理植入“懒人资深开发者“灵魂的开源插件深度拆解
程序员·github·ai编程
齐翊3 天前
怎么确认 AI 看懂了你的提示词?
人工智能·github·ai编程
李小庆3 天前
Sowork AI Agent 编程助手教程 :第一章 Python环境搭建与Sowork项目克隆学习目标
github
OpenTiny社区4 天前
🎨 看完 GenUI SDK 源码我悟了!
前端·vue.js·github
千寻girling4 天前
一份不可多得的《微服务》教程
后端·面试·github