#脚本语言 #编程语言 # #简洁 #嵌入 #巨量

bin+lib spl

栈编程语言:一种简单、简洁的脚本语言

9 个版本

0.1.8 2023年8月4日
0.1.7 2023年8月4日
0.0.4 2023年3月6日
0.0.3 2023年2月25日

120编程语言

MIT 许可证

150KB
4K SLoC

"Stack Programming Language" =SPL

SPL 是一种简单、简洁的连加脚本语言。

示例

func main { mega | with args ;
    "Running with args: " print
    args:iter
        { str | " " concat } swap:map
        &print swap:foreach
    "" println
    println <{ "and with that, we're done" }
    0
}

"5分钟" SPL:in

  • def 引入一个变量。

    def a
    
  • 写入一个常量将其推入栈。这适用于字符串和数字。

    "Hello, World!"
    
  • 使用 =<name> 将最高值分配给变量。在这种情况下,是 "Hello, World!"

    =a
    
  • 这可以写为单行 - 换行符始终是可选的,等于一个空格。

    def a "Hello, World!" =a
    
  • 变量由两个函数组成: <name>=<name>。使用 <name> 再次获取值。

    a
    
  • 使用 print 函数打印值。它从栈中取出一个值并打印它而不换行。要带换行符打印,请使用 println。末尾的分号表示 '如果此函数返回任何内容,则丢弃它'。这可以用于字符串使其成为注释,但不适用于数值常量。

    println;
    
    Hello, World!
    
  • 使用 func 关键字引入一个函数。 { mega | 是返回类型声明,在 SPL 中是在块内完成的。在这种情况下,我们的函数返回一个 mega 类型的值,这是一个 128 位整数。

    func main { mega |
    
  • 下面将解释 with 声明。它定义了 args 参数。

    with args ;
    
  • 现在,我们可以像以前一样编写代码

    def list
    
  • SPL 有一个可变长度的数组类型,列表。要创建任何构造(对象),我们使用 :new

    List:new =list
    
  • 要将元素添加到列表末尾,我们使用 push。所有构造方法都像之前的 new 例子一样,以冒号结尾。

    "Hello," list:push
    

    注意小写的 list,因为我们是在变量中的构造体上执行 push 操作。

  • 现在,我们也将 "World!" 推送到列表中。

    "World" list:push
    

    太棒了!我想现在就打印它,但怎么办呢?

  • 我们目前无法直接打印列表(根据我们所知),但我们可以遍历它!

    { | with item ;
        item print;
        " " print;
    } list:foreach;
    "" println;
    

    这里有很多东西需要解释!

    • { | 创建了一个没有返回类型的闭包(在 C 风格语言中,这将是一个空函数)。
    • with item ; 声明参数。这是可选的,如果函数不需要参数,则不需要。运行 "a" "b" "c" 并调用一个带有 b c ; 的参数,将每个字母留在相应的变量中。
    • 我们 already know what print does - it prints the item and a space in this case.
    • 分号表示我们不在乎打印的结果。在这种情况下,打印不返回任何内容,但我添加分号只是为了清晰或以防万一。
    • } 结束闭包,并将其放在我们的栈顶。
    • list:foreach 调用我们的 list 上的 foreach 方法,该方法使用 callable this ; 声明 - 这意味着我们需要提供与隐含的 this 参数(它可以是任何名称 - 解释器根本不在乎名称 - this 只是惯例)的一个参数。这里的 callable 不是一个类型!
    • foreach 也不返回任何内容,但我添加分号是为了清晰。
    • 然后打印一个换行符。
    Hello, World! 
    
  • SPL 使用 <lower> <upper> Range:new 创建范围。你可以遍历它们。

    0 5 Range:new:iter
    
  • 现在,让我们将这些值都乘以 5。

        { mega | 5 * } swap:map
    

    等等,怎么了?为什么方法调用突然不一致,迭代器没有被调用,它现在变成了别的!

    确实看起来如此,不是吗? swap 交换栈顶的两个值。 a b -> b a。这意味着我们实际上是在调用迭代器。在调用之前,闭包和迭代器被交换了。swap:map 是写 swap :map 的更简洁方式。

    迭代器上的 map 函数(通常通过大多数集合构造的 :iter 获取)用于将函数应用于迭代器中的所有项。这里的闭包实际上接受一个参数,但省略了 with 声明。较长版本的代码将是

        { mega | with item ;
            item 5 *
        }
    

    但是这相当笨拙,所以当参数直接传递给下一个函数时,它们通常只是简单地保留在栈上。代码中的*是一个接收两个数字并将它们相乘的函数。同样适用于+-%/。在其它语言中,a b -相当于a - b。而ltgteq用于比较值。

    返回值只需在函数退出时在栈上留下一些东西即可,返回声明实际上可以省略,但是分号无法确定要丢弃的结构数量,所以除非你绝对确定,否则不应该这样做。在这种情况下,我们绝对确定它永远不会用分号调用,因为映射迭代器除了返回对象之外没有其他用途(这在实践中大多数闭包都是这种情况),因此我们可以省略返回类型声明,并得到{ | 5 *}。真 neat!

  • 我们可以像使用数组一样在迭代器上使用foreach。使用_str将数字转换为字符串。

        { | _str println } swap:foreach
    
    0
    5
    10 
    15 
    20
    

    范围包含下限,但不包含上限。它们通常与其他语言的等效伪代码类似使用

    for(int i = 0; i < 5; i++) { println((String) i * 5); }
    
  • SPL实际上并不完全是可拼接的。它还支持后缀参数

        println <{ "and with that, we're done" }
    

    这实际上不是一个特殊解释器的功能,而是一个特殊词法分析器的功能。这与非后缀版本100%等效,其中字符串在println之前。

    同样也可以用于对象调用。让我们用后缀重写之前的代码

    Range:new <{ 0 5 }
        :iter
        :map <{ { | 5 * } }
        :foreach <{ { | _str println } }
    

    我骗了。现在这不再100%等效了。让我们看看底层发生了什么。

    call Range
    objpush
    const mega 0
    const mega 5
    objpop
    objcall new
    objcall iter
    objpush
    const func 0
      const mega 5
      call *
      end
    objpop
    objcall map
    objpush
    const func 0
      call _str
      call println
      end
    objpop
    objcall foreach
    

    您可以看到现在有objpushobjpop指令。这正在做我们之前示例中swap所做的事情。然而,swap只能交换最顶部的值,但后缀参数允许任何数量的值。这就是为什么有一个专门为此设计的指令。它也可以通过AST修改来使用,但在正常语言使用中无法获取它,因为它可能会导致解释器恐慌。

    objpushobjpop在名为objcall栈的单独栈上操作,而不是主对象栈。

更多教程内容将在后面提供。

SPL中的Rust嵌入

由于SPL几乎不完整的标准库,因此需要嵌入Rust来执行许多任务。可以按以下方式执行:

> cat rust-test.spl
func main { |
	1 rusty-test _str println
	0
}
func rusty-test @rust !{
	println!("hii");
	let v = #pop:Mega#;
	#push(v + 1)#;
}
> spl --build rust-test.spl demo
---snip---
> ./spl-demo/target/release/spl-demo rust-test.spl
hii
2

如您所见,这相对直接;但当前有一些主要限制

  • 这是一个新二进制文件,不能链接到当前运行的程序
  • 目前不能自动添加crates

第二个问题容易解决,但我打算先解决第一个问题。遗憾的是,解决这个问题需要将代码编译为动态库,并且还需要让它与正在运行的程序一起工作。如果有人知道如何正确地这样做,我会非常感谢一个PR或issue,解释它。

依赖项

~54KB