欢迎光临散文网 会员登陆 & 注册

LoveString基础

2022-08-13 03:45 作者:Fuxfantx  | 我要投稿

注意:我不打算实际构建这门语言!

设计目标

完全基于Dictionary、仅有String类型的语言,无敌炫酷的玩具。


先来个Hello World吧!

func init:

    print: "Hello World!"


从了解符号开始

  1. 两种引号,[]围住的部分为Key""围住的部分为Value;=作为赋值符号使用。

  2. #LF围住的部分将被视为注释。

  3. %为“Value化”符号,用于转义和格式字符串。

    print: "%"%""  #""

    print: "My name is %s, welcome to learn %s%s." % "John""Love""String"

    # My name is John, welcome to learn LoveString.

  4. ()围住的部分称为优先行,用于提高运算优先级,以及跨行书写同一语句;优先行会尝试捕获一个Value。

  5. Space用于分隔关键字。因此,各个关键字之间必须存在空格(除非换行)。

  6. LF用于分隔语句(即:每行代表一个语句),Tab用于表明代码的局部级别。

  7. 要求以:作方法关键字的后缀,唯一例外是func:(定义一个匿名函数)

=为从右向左结合,其余关键字全部为从左向右结合。


LoveString的字典

入手字典,先从KeyValue开始。可以简单地把Key理解为变量名,Value理解为变量值,但实际上更为简单。

一般的字典携带一系列的键值对键键对,现在我们来定义一个一般的字典:

root[My First Dictionary] = {

    [1] = "Love"

    [2] = "String"

}

root关键字代表对根字典进行操作,后面我们会对根字典作出详细的说明。

由于空格分词,{不要换行;由于字典结束是一个单独的语句,}需要换行。


添加键,删除键

不存在的Key对应的Value是"",即空字符串。因此:

  • 添加一个键,只需访问一个不存在的键,然后赋值;

  • 删除一个键,只需将一个存在的键赋为空值。

示例如下:

func init:

    root[my_new_key_1] = "Love"

    root[my_new_key_2] = "String"

    root[to_be_erased] = "Foobar"

    root[to_be_erased] = ""

    print: [undefined_key]  #输出一个空行


方法

方法是代码段的封装,可以用于支持外部语言、实现代码复用、标记程序入口等。现在我们来定义一个一般的方法:

func my_method: [var1]

    [var2] = "String"

    print: ([var1] + [var2])

然后执行它:

func init:

    my_method: "Love"

# LoveString

从定义过程中可以看出,方法包含一个自定义关键字、一个内部字典和一系列的语句。以func开头的行,将检测第一个空格后的关键字名,并对第二个空格后的键进行“导出”。

执行方法时,内部字典从空字典开始,先将传入的键/值写入内部字典,然后执行其后的一系列语句。最后,把内部字典全部置空。

使用return关键字能终结方法的执行,并“导出”一个局部变量,这点与其它语言是一致的。

特别地,使用return_self可以保证方法“逐级蚕食”后面的方法,理解这点需要一些匿名函数的知识。


键名是静态的

遇到这样一个代码段怎么办?

func my_add_root: [sth]

    root[sth] = [sth]

答:将传入的[sth]键/值写入根字典的名为sth的键。

我们强烈不建议用动态键名扩充字典,但真的想这么做的话,可以使用add家族的方法:

func my_dynamic_add: [name][value]

    add_root: [name][value]

    add_to: root[dynamicadds][name][value]


func init:

    my_dynamic_add: "my_new_key_1""Love"

    my_dynamic_add: "my_new_key_2""String"

    print: (root[my_new_key_1] + root[dynamicadds][my_new_key_2])


# LoveString

add_root方法取第一个参数的最终值作为键名,将第二个参数以引用形式存储到根字典中。

add_to方法相较第一个方法在一开始多一个“目的地”。


默认值参数

考虑LoveString“未定义即空”的特性,我们允许执行方法时传入任意项参数,但需要注意赋值的目标问题:

func 123: [1][2][3]

    root[1] = [1]

    root[2] = [2]

    root[3] = [3]


func init:

    123: "Love""String"

    print: (root[1] + root[2]) #LoveString

    print: (root[1] + root[3]) #Love

    123: "Love""""String"

    print: (root[1] + root[2]) #Love

    print: (root[1] + root[3]) #LoveString

    123: "Love""""""String" #在这里,"String"被忽略了

    print: (root[1] + root[2]) #Love

    print: (root[1] + root[3]) #Love

为此,我们提供一个小小的语法糖,方便定义函数的默认值:

func 123: [1][2][3]

    default[1] = "Love "

    default[2] = "String "

    default[3] = "Forever"

    return ([1] + [2] + [3])

效果如下:

func init:

    print: (123:)  #Love String Forever

    print: (123: "Love""String")  #LoveString Forever

    print: (123: "Love""String""")  #LoveString


传入一个方法,还是一个值?

上一节中,您可能已经注意到我们对于嵌套方法的处理方式了:总是用优先行围住先执行并传值的方法。

如果不这样做会怎么样?下面给出一个有趣的例子:

func sth_lambda: [1]

    print: "I am a Lambda!"

    print: [1]


func init:

    print: sth_lambda: "Not Ignored"


# ---------------- 

# func sth_lambda: [1] 

#     print: "I am a Lambda!" 

#     print: [1] 

# ---------------- 

# Not Ignored

在此例子中:

我们向print:方法传入了sth_lambda方法,于是print方法打印出了sth_lambda方法的定义,并返回了一个print: 匿名函数。

之后,新的print:匿名函数与字面量"Not Ignored"结合,于是打印出了一行"Not Ignored"语句。

怎么调用一个传入的方法呢?这里也只需要使用优先行:

func method_to_be_called:

    print: "Called"


func method_passer: [method]

    if ([method] isfunc) -> func:

        ([method]) #这样书写,传入的方法就会被执行

        root[method] = [method]

    else -> return


func init:

    method_passer: method_to_be_called:

    (root[method]) 

# Called

# Called

之后再展开讲解程序的选择与循环结构。


传入两个方法,还是遵照传递性进行运算?

始终记住,分词器依赖空格工作,方法的各个参数之间不用空格分割。

因此:

myfunc: func1:func2: 是向myfunc:传入两个方法;

myfunc: func1: func2:是向myfunc:传入方法func1:,如果返回了方法,则向返回方法传入func2:;否则func2: 将被无视。

print:在设计时同时支持return_self式的运算符重载以及任意数量的可变参数,因此print:后各个参数间写空格与不写空格等效。


值传递与引用传递(了解即可)

阅读到这里就可以展开讲解参数传递方式了。

LoveString对于“需要生成新值”的行为采取值传递,即传递字典的值;

对于其他行为,如赋值、函数传参,则使用引用传递

如何实现引用传递呢?这里将介绍我们的内存模型。

程序初始状态下存在一个root_map,存储了Key的hash和字面量/字典的引用。赋值过程的流程如下:

  1. 判断赋值动作是Key-Key型,Key-Value型,还是Key-Dict

  2. Key-Key型:对右侧Key进行hash处理,遍历root_map来获得该key对应的引用;然后对左侧key进行hash处理,在root_map新增左侧Key-hash的记录;

  3. Key-Value型:开辟内存写入Value,然后在root_map新增一条记录;

  4. Key-Dict型(将键与字典字面量绑定):为该字典字面量开辟submap,进行Value写入;root_map内新增内容为Key-Hash、submap引用的记录。

每个Value和Submap均绑定一个引用计数。每个赋值动作均会导致对旧Value/Submap和新Value/Submap的引用计数变化与检测。引用计数归零时,该Value/Submap会被释放;Submap的释放过程中,其内各Value的引用计数均会减一。


循环引用问题(了解即可)

考虑如下的代码段:

func init:

    root[A] = {

       [sthB] = ""

    }

    root[B] = {

       [sthA] = root[A]

    }

    root[A][sthB] = root[B]

这个代码段将按以下顺序工作:

  • root_map中新建A,然后构建Submap(A),内含的sthB指向空字符串。

  • root_map中新建B,然后构建Submap(B),内含的sthA对应了Submap(A)的引用。

  • 修改Submap(A)中的sthB使包含对Submap(A)的引用。

这时两个Submap彼此引用,导致引用计数无法归零。为了解决这样的循环引用问题,每个Map(root_map或Submap)会预先包含一个SubmapList供赋值时检查。

循环引用的赋值时检查仅在Debug模式工作。

检查到循环引用时,程序会形成一个断点。请手动修复这样的循环引用问题。


根字典

从了解符号开始一节中,提到了用func: 定义匿名函数的方法。现在我们可以对字典的定义作一点简单的扩充:

字典是一个记录了一系列Key - Value引用的Map(root_map或Submap),Key是一个hash化的名称,Value包含了对String字面量的引用、对Submap的引用、匿名函数,三者之一。

到此,可以给出根字典的定义:

根字典是字典的超集。除了Key - Value关系表之外,根字典允许您使用func定义具名方法。因此,具名方法不能嵌套定义。此外,根字典自带一个叫init:的虚方法,其在全局变量全部加载完成后自动执行,作为程序入口而存在。

init:虚方法可以携带参数。

LoveString将每个程序文件视为一个根字典,没有通过脚本新建根字典的方法。


运算符

作为高级语言,总不能仅仅赋一堆值就完事了吧?

于是我们尝试引入一些运算符。在此提醒两点:

  • 运算符是特殊的关键字,空格不能少!

  • 关键字出现结合失败的情形时,其后的关键字和元素都会被忽略。

上文介绍了%的两种用法,在这里作出简单的补充:

  • %% :形成一个百分号

  • %" : 形成一个引号

  • %n :换行

  • %t :Tab缩进

  • %s :字符串替换符

  • 对%s作格式替换的例子:

    [sth] % "Value1"[Value2][Dict3](func4:)func5:func6:

    依次为:String字面量"Value1",[Value2]对应的值,[Dict3]对应Submap的文本输出,方法func4:的值,func5:``func6:的定义。

接下来引入几种布尔运算符(优先级上高下低左高右低):

  • == != :等值检测

  • and or not:逻辑与/或/非

  • in:若前者为Key,后者对应字典,且后者对应的字典包含了该Key,则返回true

    • 使用self访问方法的内部字典。

  • isstr``isdict isfunc:类型判断,分别对应String/字典/函数

  • isroot:检查根字典是否有对该字面量的引用

    • isroot_deep:检查根字典及其子字典是否有对该字面量的引用(很慢)

对于布尔运算符的返回值,作出如下约定:

  • 空字符串代表“非”,含内容的字符串代表“是”(一般记录为"true")

  • 关键字false代表“非“,true代表“是”

高级优先行

高级优先行是特殊的优先行,是LoveString扩展模块的有力语法支撑。官方计划维护以下几种高级优先行:

group()是字典结构的语法糖:

root[current_color] = group("1.0""1.0""1.0""1.0")


# 相当于

root[current_color] = {

    [1] = "1.0"

    [2] = "1.0"

    [3] = "1.0"

    [4] = "1.0"

}

此外,group()为方法提供了可变参数支持:

func grouped_function: group()

    for [i] in self -> func:

        print: [i]

    return "That's all, thanks."


func init:

    print: (grouped_function: "LoveString""Mutable Amount of Arguments")


# LoveString

# Mutable Amount of Arguments

# That's all, thanks.

math() 会将传入的Value全部转成数字(失败时则转成0),并提供了一系列符合数学书写习惯的表达式:

root[math_instance] = math( 3^2 + 6%5 + root(4)by(2) + exp(ln2) )


func init:

    print: root[math_instance]


# 14

注意:该高级优先行使用了不同的分词器,以尽可能符合正常数学表达式的书写习惯。

duo()允许传入两个Value和一个高级运算符(“先计算再赋值”运算符,以及大小比较类运算符),Value会被转换为数字:

root[my_set] = duo( root[my_advanced_pass] = "5.0" )

root[my_set] = duo( root[my_set] += "1")

root[compare] = duo( root[my_set] > "5" )


func init:

    print: root[my_set]root[compare]


# 6.0

# true

在循环结构中还会再介绍几种高级优先行。


匿名函数与“导向“算符

现在,我们掌握了了基于自定义关键字的方法。教程的一开始我们也透露了用func:关键字定义的匿名函数,怎么把这两种元素统一起来呢?

现在我们可以使用“导向”算符-> 帮助我们完成此操作。“导向”算符用于向匿名函数传参,并将具名方法等连接到匿名函数或优先行:

root[myfunc] = func: [a][b][c]

    if ([d] in self) -> (print: [d])

    print: [a][b][c]

    if (not [c] in self) -> (print: "%n--String Lover)


func my_func: [d] -> root[myfunc]


func init:

    print: (root[myfunc] -> "Love""String""Forever")

    print:

    print: (my_func: "I""love""string""forever.")

# Love 

# String 

# Forever 

# --Srting Lover 


# I 

# love 

# string 

# forever.

通过“导向”连接具名方法和匿名函数时,具名方法的参数、匿名函数的参数都是内部字典的Key。这允许你将匿名函数导向具名方法时,要求具名方法传入更多的参数。

从示例中还可以挖掘到我们设计的语法糖:具名方法“多出来”的参数可以流入导向的匿名函数内。

注意:

  • 没有匿名函数“导向”匿名函数的语法,“导向”的左侧若是匿名函数,则向匿名函数传参。

  • 如果不导向具名方法的话,不装入优先行的匿名函数不会执行。

  • LoveString禁止了具名方法执行时导向参数,以保证具名方法的统一性和分词器设计的简洁性。

结构式

本节中,介绍程序设计中非常关键的选择结构及循环结构。

先从结构式开始:结构式是形如condition -> callable的语句,callable允许无参关键字、具名方法、匿名函数及优先行;为了降低语法复杂度,结构式自身不能向callable传入自定义参数。但是,我们可以给出一个“结构式传参”的示例:

root[arg_to_structureline] = func: [a][b][c]

    foo: [a][b]

    bar: [c]


func init:

    if (true) -> (root[arg_to_structureline] -> root[a]root[b]root[c])

选择结构的结构式是if () -> callable

强制要求用优先行(包含高级优先行)表示条件,以确保能捕获到返回值。

我们深知匿名函数是一种相当晦涩的设计,因此大多数情况只需使用如下的写法:

# 单行

func init:

    root[input] = receive_and_pause:

    if (root[input] == "hp+1") -> duo(root[hp] += 1)

    else -> return

    return_self


# 多行

func init: [sth]

    if ([sth] == "") -> func:

        [sth] = "String"

        [lover] = "Lover"

        print: ([sth] + " " + [lover])


# 该多行示例的输出结果(执行时不传参):String Lover

注意:结构式不自带字典,但匿名函数自带字典!

来到while循环,我们提供了while_true () -> callablebefore () -> callable两种结构式,见名知义。

for家族则更加常用一些,我们提供了以下几种遍历模式:

# 字典遍历模式

for [i] in root -> func1:

for "sth" in root -> func2:

deepfor [i] in root -> func3:

deepfor "sth" in root -> func4:


# 高级优先行模式

for "Values" in range("0""100") -> print:

for "Values" in ftb("0""100""10") -> print:

可以看出for家族的通式:for A in B -> callable

其中,A如果是Key的话,会自动向“导向”的callable传入一个Key;如果是Value的话,则传入一个键名字面量。

字典遍历模式中,B一般是指向字典的元素,以rootself居多,也可以是自定义字典。

    如果B部分是空字典的话,该循环被跳过。

    如果B部分是一个Value的话,for循环将会执行一次,此时传入的都是该Value。

对于高级优先行模式,range()优先行要求内含两个数字,左闭右开,效果如该例:

func init:

    for "Values" in range(0,2) -> print: 


# 0

# 1

ftb()是"from-to-by"的缩写,ftb([A][B][C])表示从[A]开始(左闭),到[B]结束(右开),每次步进[C]个单位,效果如该例:

func init:

    for "Values" in ftb("0""50""10) -> print:


# 0

# 10 

# 20 

# 30 

# 40 

deepfor比较少用,但可以把传入的字典所包含的子字典一并遍历。高级表达式模式中,fordeepfor完全等价。


在LoveString中运用OOP思想

  • 通过多层次的字典设计,可以实现面向对象的封装性。

  • 通过编写“模板”,可以形成实例,也可以完成继承。例如:

    root[Templates][Character] = {

  •     [HP] = "100"

  •     [MP] = "75"

  • }

  • root[Templates][Wizard] = (

  •     root[Templates][Character] + {

  •         [MP] = "200"

  •         [Skill] = "Magic"

  •     }

  • )

    对于字典,+是合并运算符,深拷贝右侧字典,然后对左侧字典顺次赋值改键。

  • “成员变量私有化“

    子字典中加入[lock] = "true"[lock] = "before_addup"这样的键值对,则只能通过向上一级的匿名函数传参来访问该子字典。

    仅在Debug模式起作用。

Congratulations!


LoveString基础的评论 (共 条)

分享到微博请遵守国家法律