基本用法
FVV 全称 Fresh View for Viewer,是一个较为简单易上手的语言,名字显而易见,是个 废物 清新 的文本格式,那么有多清新呢,请看示例
<基本类型>
BoolVal = true <布尔值>
IntVal1 = 1 <十进制整数>
IntVal2 = 1000'0000 <有引号的十进制整数>
IntVal3 = 0x5 <十六进制整数>
IntVal4 = 02 <八进制整数>
IntVal5 = 0b0 <二进制整数>
FloatVal = -1.0 <浮点数>
StrVal = "字符串" <字符串>
<列表>
StrList = ["1", "2", "3"] <字符串列表>
<不再多写...>
<组>
Group = {
SubVal = "子值" <子值>
SubGroup = {
SubVal = "子值" <子组子值>
}
} <普通组>
GroupList = [
{
SubVal = "组一子值"
} <组一>
{
SubVal = "组二子值"
} <组二>
] <组列表>
<赋值>
Value1 = StrVal <常规赋值>
Value2 = "字符串" <Value1>
<这里把注释给赋值了,注释的赋值只会生效于目标是字符串类型的情况,不会隐式转换>
List1 = [Value1, Value2] <常规列表赋值>
List2 = [List1] <列表赋值列表,会自动展开>
Group1.SubVal1 = Value1 <通过点连接组名赋值到子值>
Group2.SubVal = Group1.SubVal1 <用同样的方式赋值到另一个子值>
Group1 = {
SubGroup = {
SubVal2 = SubVal1 <展开赋值>
}
}
<赋值遵守就近原则,并且赋值的字符串将按原样存储>
<仅有常规赋值会保留原样,列表或注释中的赋值不会保留>
<拼接与类型提升>
StrVal1 = "字符串" + "字符串" <常规拼接>
StrVal2 = StrVal1 + true + 1 + 1.0 <所有非字符串基本类型进行拼接都会提升为字符串>
<解析时不会记录拼接的原样,所有拼接中的赋值都会丢失>
StrList1 = ["字符串" + "字符串"] <拼接在列表中也可用>
StrList2 = [StrVal1, StrList1, true, 1, 1.0] <在列表中也会提升类型为优先级最高的字符串>
FloatList1 = [true, 1, 1.0] <字符串之下是浮点数>
IntList1 = [true, 1] <浮点数之下是整数>
BoolList1 = [true] <整数之下只有布尔值了>
<组不支持拼接,列表也不支持拼接(因为已经支持在列表中展开其他列表了)>
<并且列表仅允许单一类型,组与基本类型不可以混合存储>
<分隔符>
Val1 = 1 <用换行省略了分号>
Val2 = 2 <显式的分号声明>;
ValList1 = [
1
2
3
] <用换行省略了逗号>
ValList2 = [
1,
2,
3,
] <显式的逗号声明(末尾是否有逗号不影响解析)>
<其他字符>
Key1: "Value" <冒号与等号等价>
Key2:"Value" <全角冒号同样可用>
IntVal = 1000’0000 <有全角引号的整数>
Say1 = "\"Hello\" “Hello”" <常规字符串>
Say2 = “"Hello" “Hello\”” <用全角引号包裹的字符串>
<可以看到转译也会因此改变>
Say3 = `
"Hello" “Hello”
` <用反引号包裹的原始字符串,解析时会去掉开头与结尾的空白,并且去掉最小缩进>
Grp = {Lst1 =[Key1, Key2];} <全角括号>
Lst2 = ["Value",];<全角分隔符>
<除引号外,其他全角字符均可与半角字符混用>
显而易见,支持字符串、布尔值、整数、浮点数以及它们的组的定义,还有 FVV 本身的组,还支持值组以及赋值
FVV 本身在实现方面有部分缺陷,对转义的判断仅为当当前符号是特定符号时(例如 " 和 >),
判断其上一个字符是否为 \,并没有很完善的转义判断,故仅支持转义 " 和 >(即 "\"" 和 <\>>),
不支持 \n 等转义,直接使用 \ 时不需要 \\,直接 \ 即可(因此字符串或注释的末尾不可以是 \)
值的命名也是没有什么忌口的,但需要注意的是,
如果一个值被命名为纯数字(仅 整数,因为 浮点数 会被分割为双层组),
那么这个值将无法用于赋值,因为没法判断到底给的是值还是值的名称
同样的,还需要注意正常命名不要带 . 啊喂,会被认为是多层组的定义的
值的定义使用 ; 或 换行 进行,
所有的值都可以置于根路径的 {} 里面(放不放都没区别,只不过加一个 {} 看起来类 JSON 一点),
块注释使用 <>,没有行注释
换行 可以省略掉以下字符:
;: 当定义值时有换行则无需在行尾添加;,: 当定义组时有换行则无需在两个值之间添加,
注释是 FVV 一个比较特色的功能,它可以放到任何地方,请看示例:
<注释>{<注释>a<注释>=<注释>1<注释>;<注释>}<注释>
可以非常直接地看出来,注释完全是想怎么写就怎么写,但是它的作用不止于此
介于 = 与 ;(或 换行)之间的最后一个注释,将会被认定为是该值的 描述,在解析时将会被留存为该值的附属值
因为 换行 可以替代 ;,描述 又是和定义一样通过 换行/; 来解析的,
所以 描述 不可以写到定义的下一行!
为了让 FWW 有更好的开发体验,FVV 值组 及其子值的 描述 是放在前面的
描述 是支持赋值的,也就是说任意字符串都有可能赋值到 描述 上,只要 描述 与该字符串的值名称相同
所以在实践中,建议在编写 描述 时,在开头加一个 - ,从而实现类似于 <- 这是注释> 的效果,
如果字体支持,<- 会变成一个好看的箭头,这样既不会与赋值机制有冲突,还可以提升观感
代码层使用方法
仅支持 UTF-8 文本(或 UTF-8 with BOM),由于各种语言实现的差异,不确保所有语言均可正常解析所有文本
解析时会把所有 \r\n 替换为 \n,把所有 \r 替换为 \n
(即强制将文本转换为 LF 文本)
引入依赖
- C++
- Dart
- Go
- Kotlin
在项目中添加文件后,直接 #include 即可:
#include "fvv.hh"
先添加依赖:
- Dart
- Flutter
dart pub add fvv
flutter pub add fvv
然后直接 import 即可:
import 'package:fvv/fvv.dart';
在 go.mod 中通过 replace 指向本地路径,并通过 require 添加依赖
replace app.niggergo.work/fvv => path/fvv/go
require app.niggergo.work/fvv v0.0.0
通过命令行添加依赖:
go get -u app.niggergo.work/fvv@latest
远程依赖仅可在 Go 1.25+ 可用!
直接 import 即可:
import "app.niggergo.work/fvv"
先依赖源中有 PkgPub 的依赖源
然后添加依赖:
dependencies {
implementation("ren.shiror.fvv:core:1.+")
}
再直接 import 即可:
import ren.shiror.fvv.FVVV
基本功能
首先当然是解析
由于 Go 的语言特性,其函数名称不得不大写
由于 Dart 与 Go 不支持函数重载,为了保证长期维护的可能性,额外添加了 String 后缀
- C++
- Dart
- Go
- Kotlin
FVV::FVVV fvv;
fvv.parse(txt);
final fvv = FVVV()..parseString(txt);
fvv := NewFVVV()
fvv.ParseString(txt)
val fvv = FVVV().apply { parse(txt) }
然后是格式化成字符串,当传入以下内容时,会有不同的格式化效果:
Common: 默认Min: 格式化后的文本将去掉任何符号间的空格、缩进、换行(替换成;)、描述BigList: 格式化后的文本中的任何组都会被展开为一行一值NoDesc: 格式化后的文本不再有任何描述
各个语言的格式化选项位置如下:
C++:FVV::FormatOptDart:FVVFormatOptGo: 以FmtOpt开头的值,调用时需要通过PrintOpt传入Kotlin:FVVV.Companion.FormatOpt
不同的语言在命名上略有不同,但实际行为一致
FVV 值组 的 描述 在 FWW 中是页面标题,在 Min 与 NoDesc 中不会被去除
其内部的 FVV 值 的 描述 则是其组件类型,同样也不会被去除
一切去除描述的行为均仅为在输出的字符串中去除,不会影响到代码中的描述
- C++
- Dart
- Go
- Kotlin
string fvv_str = fvv.print();
final fvvStr = fvv.print();
fvv_str := fvv.Print(PrintOpt{})
val fvvStr = fvv.print()
然后是对值的基本判断
由于 Go 的语法可用性太低了,便又封装了 SubIsEmpty 和 SubIsNotEmpty 来判断子项是否为空,
但使用前最好还是先判断一下值本身是否为 nil
由于写 Kotlin 时语法基本上照抄 Dart,所以 isEmpty 和 isNotEmpty 是通过 getter 获取的
- C++
- Dart
- Go
- Kotlin
bool is_empty = fvv.isEmpty() && !fvv.isNotEmpty(); // 判断是否为空
bool is_type = fvv.isType<T>(); // 判断类型
final isEmpty = fvv.isEmpty && !fvv.isNotEmpty; // 判断是否为空
final isType = fvv.isType<T>(); // 判断类型
is_empty := fvv.IsEmpty() && !fvv.SubIsNotEmpty() // 判断是否为空
_, is_type := fvv.Value.(T) // 判断类型
val isEmpty = fvv.isEmpty && !fvv.isNotEmpty // 判断是否为空
val isType = fvv.isType<T>() // 判断类型
然后是读取值
由于 Go 的语法实现这些有点麻烦,所以请自行使用 .(T) 来获取值吧
由于 Dart 机制,常规获取 List 类型得到的是拷贝,有 Ref 后缀的函数获取到的才是引用
由于 JVM 机制,在 Kotlin 获取 List 类型的方法有点抽象...,在 Kotlin 获取到的是拷贝
- C++
- Dart
- Go
- Kotlin
auto value = fvv.as<T>(); // 指定类型获取
string str = fvv.asString(); // 获取字符串
vector<string> strs = fvv.asStrings(); // 获取字符串组
// 其他依此类推,不再示范
final value = fvv.as<T>(); // 指定类型获取
final str = fvv.asString(); // 获取字符串
final strs = fvv.asStrings(); // 获取字符串组
// 其他依此类推,不再示范
value, ok := fvv.Value.(T) // 指定类型获取,通过返回的布尔值来判断是否获取成功
val value = fvv.as<T>() // 指定类型获取
val str = fvv.asString() // 常规获取字符串
val str = fvv.string // getter获取字符串
val strs = fvv.asStrings() // 常规获取字符串组
val strs = fvv.strings // getter获取字符串组
// 其他依此类推,不再示范
由于 Dart 的基本类型名称会冲突,所以没在它上面实现 getter 获取...
赋值只需要在有重写运算符功能的语言上直接赋值即可,没有则需要手动调用内部的值进行赋值