6个版本
0.1.6 | 2022年6月17日 |
---|---|
0.1.5 | 2022年6月10日 |
0.1.4 | 2022年4月4日 |
0.1.3 | 2021年9月18日 |
#601 in 编程语言
92KB
2K SLoC
IMPRAL
IMPRAL是一个针对类似LISP方言的命令解析和评估库,旨在为大型应用、游戏和框架内的合理易用和专门的命令行输入提供支持。
注意
目前不完整/仍在开发中。预期会有破坏性变更。
不要在生产代码中使用。
介绍
功能
简要概述
- 类似于LISP/Scheme的基本语言。
- 大多数情况下安全且无panic的解析器。
- 仅将AST转换一次。
- 一组相对较小的字面类型...
_
null
- 布尔值 (
true
/false
) - 字符
- 整数和浮点数(都是64位)
- 整数基数:
0b…
、0o…
、0d…
、0x…
- 整数基数:
- 字符串(在24字节以下紧凑)
- 包括裸词!
字节数组- UUID的,以
U
为前缀。
- 数据结构
- 列表(例如:
[ 1, 2, 3 ]
,逗号可选) - 字典(例如:
{ a=1, b=2, c=3 }
,逗号可选) - 基数数字列表(
0x[C0 +FF -EE]
)
- 列表(例如:
- 引用!
- 结果引用:
$
- 上下文引用:
$$
- 局部引用:
$my-ref
- 全局引用:
@my-ref
A && B
:如果A
成功,则仅运行B
。A || B
:如果A
失败,则仅运行B
。
- 结果引用:
- 运算符!
- 算术运算(
+ - * / **
) - 相等性(
== != < > <= >=
) - 其他(
? ! ~ ^ ++ --
)
- 算术运算(
- 字段和索引!
- 任何后跟点和名称的项目都是一个字段访问:
_.name
- 任何后跟括号表达式的项目是一个索引访问:
_[index]
- 任何后跟点和名称的项目都是一个字段访问:
- 范围!
- 任何两个不是范围的表达式,通过两个点分隔。
- 可以通过在点之后添加一个
=
来设置可选的“最后包含”标志。 - 例如:
_ .. _
和_ ..= _
- 管道!
- 第一个表达式是源
- 通过
|
分隔的表达式 - 过滤器:
… |? bar | …
- 链式操作:
… | … |? … | …
- 可能失败的操作!
- 任何后跟
?
的表达式都会展开到默认值。 - 任何后跟
?!
的表达式将引发错误。
- 任何后跟
- 位置参数:
foo _ _
- 命名参数:
foo bar=_ baz=_
- 子表达式,用
(…)
括起来 - ...以及其他小东西!
语法 & 语义
字面量
字面量是一个简单的值,比如数字、字符串、布尔值等。
以下是可能的字面量列表
- Nothing:值的缺失;写作
null
。 - 布尔值:有
true
和false
。就是这样。 - 数字:数字可以以多种方式书写...
1337
-1
42.69
1.0e-5
0b101010
0xC0FFEE
- 裸词:裸词是任何完全由字母、数字、
_
和-
组成的字符序列,始终以至少一个字母开头。 - 字符串:任何用双引号括起来的文本!
"Hello, World!"
- 列表:列表可以通过两种方式创建...
- 通过语法:
[item1, item2, … itemN]
(逗号是可选的!) - 通过命令:
list item1 item2 … itemN
- 通过语法:
- 字典:字典也可以以两种方式创建...
- 语法规则:
{ key1: val1, key2: val2, …, keyN: valN}
键值对之间必须有一个或多个
,
;在}
之前可能有,
。 - 通过命令:
dict key1 val1 key2 val2 … keyN valN
- 语法规则:
参考
存在几种类型的参考
- 全局参考:写作
@NAME
。 - 局部参考:写作
$NAME
或$NUMBER
。 - 结果参考:写作
$
。 - 上下文参考:写作
$$
。
命令语法
该语言,像任何Lisp一样,由存储为列表的命令(函数调用)组成,其中列表的第一个项目是一个符号,表示要评估的特定命令的名称,后跟任意数量的位置参数。事物偏离之处在于IMPRAL支持命名参数,写于位置参数之后,以便于使用。
因此,一个命令由三个(半)部分组成
-
识别命令的符号。
一个独特的裸词或任何内置运算符。在命令标识符之前不允许放置位置参数或命名参数。 -
位置参数。
值的一个空白分隔列表。 -
命名参数。
一个空白分隔的key=value
对的列表;键始终是裸词。
命名参数必须写于位置参数之后。
唯一例外的是最后一个位置中的继续命令。
还可以写入布尔参数,由一个+
或-
和一个裸词作为名称组成。 -
继续命令。(可选)
另一个命令,它是最后一个位置的一个额外位置参数,写于一个:
之后。 -
命令分隔符。(可选)
如果解析器遇到一个;
,它将停止解析当前命令,无论分号之后是什么;对于序列很有用?
总结一下
- 基本命令语法:
symbol arg1 arg2 … argN
- 命名参数:
symbol … kvarg1=val1 kvarg2=val2 … kvargN=valN
- 标志参数:
symbol … +kvarg -kvarg …
- 带有继续:
symbol … …: command
子表达式
表达式可以括在括号中,用于在其他表达式内部使用,以及作为其他命令的参数:(…)
逻辑运算符
通过写入两个用 &&
分隔的命令,后者只有在前者 成功 的情况下才会执行,结果绑定到 $
:foo … && bar $ …
通过用 ||
分隔它们,后者只有在前者 失败 的情况下才会执行:foo … || bar …
这两个逻辑运算符都可以链式使用;评估将从左到右进行。
命令管道
可以将一系列表达式写成 pipe
,其中每个阶段将结果($
)传递给下一个阶段:players |? < $.health 50 | heal $
如果某个阶段返回可迭代的对象,将评估该迭代器并将其项目传递到管道中,而不是迭代器本身。
字段和索引访问
通过使用 _.FIELD
和 _[INDEX]
语法,可以访问子值。
范围
通过输入两个连续的点(..
和 ..=
用于右闭合),可以创建两个表达式之间的范围。
可抛性
通过使用 ?
后缀运算符,可以将给定值转换为默认值,如果它是 null
或错误。添加一个感叹号(?!
)会使表达式抛出错误。
待办事项
- 使用
= …
的 Pratt 解析:https://crates.org.cn/crates/pratt - 带单位的数字
- 验证
- 解释器
依赖关系
~2MB
~46K SLoC