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

相关推荐
小牛itbull2 小时前
ReactPress:重塑内容管理的未来
react.js·github·reactpress
励志成为嵌入式工程师5 小时前
c语言简单编程练习9
c语言·开发语言·算法·vim
Peter_chq5 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
hikktn7 小时前
如何在 Rust 中实现内存安全:与 C/C++ 的对比分析
c语言·安全·rust
观音山保我别报错7 小时前
C语言扫雷小游戏
c语言·开发语言·算法
小林熬夜学编程9 小时前
【Linux系统编程】第四十一弹---线程深度解析:从地址空间到多线程实践
linux·c语言·开发语言·c++·算法
墨墨祺9 小时前
嵌入式之C语言(基础篇)
c语言·开发语言
鱼满满记9 小时前
1.6K+ Star!GenAIScript:一个可自动化的GenAI脚本环境
人工智能·ai·github
躺不平的理查德10 小时前
数据结构-链表【chapter1】【c语言版】
c语言·开发语言·数据结构·链表·visual studio
幼儿园园霸柒柒11 小时前
第七章: 7.3求一个3*3的整型矩阵对角线元素之和
c语言·c++·算法·矩阵·c#·1024程序员节