基本用法
FVV 的基本用法
FVV 全称 Fresh View for Viewer,是一个较为简单易上手的语言,名字显而易见,是个 废物 清新 的文本格式,那么有多清新呢,请看示例
<基本类型>
BoolVal = true <布尔值,解析时会忽略大小写>
IntVal1 = 1 <十进制整数(整数、浮点数均不支持正号)>
IntVal2 = 1000'0000 <有引号的十进制整数>
IntVal3 = 0x5 <十六进制整数>
IntVal4 = 0o2 <八进制整数,也可写作“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]}<全角括号(用花括号省略了应该写在“Lst1”定义后的分号)>
Lst2 = ["Value",];<全角分隔符>
<除引号外,其他全角字符均可与半角字符混用>显而易见,支持 字符串、浮点数、整数、布尔值 以及它们的 列表 的定义,还有 组 本身的 列表
命名定义
组 = {
文本 = ""
文本列表 = [""]
组列表 = [{}]
}组为 组名称,文本/文本列表/组列表为 值名称""/[""]/[{}]为值,{}称为组
故基本类型名称为 字符串/文本、浮点数、整数、布尔值,组 为特殊类型
列表类型即为 字符串列表/文本列表、浮点数列表、整数列表、布尔值列表 以及 组列表
值的命名也是没有什么忌口的,但需要注意的是,
如果一个值被命名为纯数字(仅 整数,因为 浮点数 会被分割为双层组),
那么这个值将无法用于赋值,因为没法判断到底给的是值还是值的名称
同样的,还需要注意正常命名不要带 . 啊喂,会被认为是多层组的定义的
值的定义使用 ; 或 换行 进行,
所有的值都可以置于根路径的 {} 里面(放不放都没区别,只不过加一个 {} 看起来类 JSON 一点?),
块注释使用 <>,没有行注释
换行 可以省略掉以下字符:
;: 当定义值时有换行则无需在行尾添加;,: 当定义组时有换行则无需在两个值之间添加,
} 也可省略分隔符
注释是 FVV 一个比较特色的功能,它可以放到任何地方,请看示例:
<注释>{<注释>a<注释>=<注释>1<注释>;<注释>}<注释>可以非常直接地看出来,注释完全是想怎么写就怎么写,但是它的作用不止于此
介于 = 与 ;(或 换行)之间的最后一个注释,将会被认定为是该值的 描述,在解析时将会被留存为该值的附属值
因为 换行 可以替代 ;,描述 又是和定义一样通过 换行/; 来解析的, 所以 描述
不可以写到定义的下一行!
组列表 及其子值的 描述 是放在前面的描述 是支持赋值的,也就是说任意字符串都有可能赋值到 描述 上,只要 描述 与该字符串的值名称相同
所以在实践中,建议在编写 描述 时,在开头加一个 - ,从而实现类似于 <- 这是注释> 的效果,
如果字体支持,<- 会变成一个好看的箭头,这样既不会与赋值机制有冲突,还可以提升观感
代码层使用方法
UTF-8 文本(或 UTF-8 with BOM),\r/\n/\r\n 文本均可正常解析引入依赖
在项目中添加文件后,直接 #include 即可:
#include "fvv.hh"先添加依赖:
dart pub add fvvflutter 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.01.25+ 可用!通过命令行添加依赖:
go get -u app.niggergo.work/fvv@latest然后 import 即可:
import "app.niggergo.work/fvv"先依赖源中有 PkgPub 的依赖源
然后添加依赖:
dependencies {
implementation("ren.shiror.fvv:core:2.+")
}再直接 import 即可:
import ren.shiror.fvv.FVVV基本功能
对于开发体验着重在 C++ 与 Go 上以及 Dart 与 Kotlin 上,在一起的语言开发体验较为相似
解析
由于 Go 的语言特性,其函数名称不得不大写
由于 Dart 与 Go 不支持函数重载,为了保证长期维护的可能性,额外添加了 String 后缀
FVV::FVVV fvv;
fvv.parse(txt);final fvv = FVVV()..parseString(txt);fvv := NewFVVV()
fvv.ParseString(txt)val fvv = FVVV().apply { parse(txt) }格式化
当传入以下内容时,会有不同的格式化效果:
Common: 默认UseWrapper: 最外面包裹一层 花括号Minify: 最小化,移除所有 缩进、空格、换行UseCRLF: 使用\r\n换行,默认\nUseCR: 使用\r换行,默认同上UseSpace2: 使用2个 空格 作为 缩进 ,默认是\tUseSpace4: 使用4个 空格 作为 缩进IntBinary: 输出整数为 二进制 格式,默认是 十进制IntOctal: 输出整数为 八进制 格式,默认同上IntHex: 输出整数为 十六进制 格式,默认同上DigitSep3: 输出 十进制整数 或 浮点数的整数部分 时每3个数字插入一个分隔符DigitSep4: 同上,每4个数字插入一个分隔符UseColon: 使用 冒号 而非默认的 等号 作为定义符FullWidth: 将部分符号改为 全角KeepListSingle: 强制 列表 保持为一行(默认长度达到16的内容达到6则一行一个)ForceUseSeparator: 强制添加 分隔符(最小化时无效)RawMultilineString: 当 字符串 有多行时改为 原始多行缩进字符串(最小化时无效)NoDescs: 移除所有描述(在 FWW 风格 下对组列表与组无效)NoLinks: 移除所有链接FlattenPaths: 递归展平只有1个值的组FWWStyle: FWW 风格,前置组的描述
各个语言的格式化选项位置如下:
C++:FVV::FormatOptDart:FormatOptGo: 以FmtOpt开头的值Kotlin:FVVV.FormatOpt
不同的语言在命名上略有不同,但实际行为一致
传入均支持通过 |/or 传入为一个参数或通过 , 传入为多个参数
Dart/Kotlin 默认的 toString 时("$fvv"),将使用默认的格式化效果一切去除 描述/链接 的行为均仅为在输出的字符串中去除,不会影响到代码中的实际内容
fvv.to_string(opts...);fvv.toString(opts...);fvv.ToString(opts...)fvv.toString(opts...)使用值
由于 JVM 机制,在 Kotlin 上
判断/获取 列表时请使用对应的 isList<T>()/list<T>() 方法,否则会类型出错
首先是对值的基本判断
由于 Go 的语法可用性太低了,便又封装了 IsNodesEmpty 和 IsNodesNotEmpty 来判断子项是否为空,
但使用前最好还是先判断一下值本身是否为 nil
bool is_empty = fvv.empty(); // 判断值是否为空
bool is_type = fvv.is<T>(); // 判断类型
is_type = fvv.is_list<T>(); // 判断列表类型(相当于 fvv.is<vector<T>>())final isEmpty = fvv.isEmpty && !fvv.isNotEmpty; // 判断值是否为空
var isType = fvv.isType<T>(); // 判断类型
isType = fvv.isList<T>(); // 判断列表类型(相当于 fvv.isType<List<T>>())is_empty := fvv.IsEmpty() && !fvv.IsNotEmpty() // 判断值是否为空
is_empty = fvv.IsNodesEmpty() && !fvv.IsNodesNotEmpty() // 判断子值是否为空
is_type := fvv.IsBool() // 判断类型(也可使用 fvv.Is[T](fwv),此处的 fvv 是 fvv 包)
is_type = fvv.IsBoolList() // 判断列表类型(也可使用 fvv.IsList[T](fwv),此处的 fvv 是 fvv 包)
// 其他依此类推,不再示范...val isEmpty = fvv.isEmpty() && !fvv.isNotEmpty() // 判断值是否为空
var isType = fvv.`is`<T>() || fvv.isType<T>() // 判断类型(不适用于列表)
isType = fvv.isList<T>() // 判断列表类型由于 FVV 2 统一采用 int64 为整数类型,对于各语言的兼容性如下:
C++: 使用long long/vector<long long>类型, 存储时有兼容其他整数类型,但判断时需要使用long long/vector<long long>Dart: 由于没有细分类型,无变化Go: 由于Go没有重载,在判断中对int/[]int做了兼容,使用时尽量用int64/[]int64Kotlin: 在存储与判断时均有Int/List<Int>转Long/List<Long>与Float/List<Float>转Double/List<Double>
然后是读取值
由于 Dart 机制,常规获取 List 类型得到的是引用,拷贝请再调用 toList()
在 Kotlin 获取 List 类型时由于 JVM 机制无法确认类型,总会创建一个新的固定类型的
List,不会获取到引用
T value = fvv.value<T>(/*默认值*/); // 指定类型获取值
vector<T> list = fvv.list<T>(/*默认值*/); // 指定类型获取列表final value = fvv.as<T>(/*默认值*/) ?: fvv.asType<T>(/*默认值*/) ?: fvv.get<T>(); // 指定类型获取值
//(当不确定类型时,使用 as/asType,返回 T?,确定时则可使用 get,返回 T)
final list = fvv.list<T>(/*默认值*/); // 指定类型获取列表
final boolValue = fvv.boolean; // 获取布尔值
final boolList = fvv.bools; // 获取布尔值列表
// 其他依此类推,不再示范...value := fvv.Value[T](fwv); // 指定类型获取值(此处的 fvv 是 fvv 包)
list := fvv.List[T](); // 指定类型获取列表(此处的 fvv 是 fvv 包)
bool_value := fvv.Bool(/*默认值*/); // 获取布尔值
bool_list := fvv.BoolList(/*默认值*/); // 获取布尔值列表
// 其他依此类推,不再示范...val value = fvv.`as`<T>(/*默认值*/) ?: fvv.asType<T>(/*默认值*/) ?: fvv.get<T>(); // 指定类型获取值
//(当不确定类型时,使用 as/asType,返回 T?,确定时则可使用 get,返回 T)
val list = fvv.list<T>(/*默认值*/); // 指定类型获取列表
val boolValue = fvv.bool; // 获取布尔值
val boolList = fvv.bools; // 获取布尔值列表
// 其他依此类推,不再示范...由于 Dart 的基本类型名称会冲突,但为了尽可能与 Kotlin 一致,目前的 getter 如下:
bool(Kotlin)、boolean(Dart&Kotlin)int(Kotlin)、integer(Dart&Kotlin)double(Kotlin)、float(Dart&Kotlin)string、bools、ints、doubles、strings、fvvvs(Dart&Kotlin)
赋值只需要在有重写运算符功能的语言上直接赋值即可,没有则需要手动调用内部的值进行赋值
序列化/反序列化
序列化/反序列化 在各语言上均可使用,但差异较大
C++ 可以直接通过 to/from 传入值引用,或在类中定义 fvv_values 函数以绑定名称与引用
parse 支持传入第二个参数以相当于执行 parse+`to
Dart 需要定义继承 FVVStruct 的类,在 fvvValues 函数中绑定名称并设置 getter/setter,
当需要处理嵌套的 FVVStruct 列表时,需要传入 factory 以创建该 FVVStruct
调用只需要通过 to/from 传入即可
parseString 支持传入第二个参数以相当于执行 parseString+to
Go 可在 struct 使用 `fvv:"值名称"`,支持 omitempty
调用只需要通过 Unmarshal/Marshal 传入即可
ParseString 支持传入多个参数以相当于执行 parseString+Unmarshal
Kotlin 基于 serialization 实现
parse 支持传入类型以相当于执行 parse+Unmarshal
拓展插件
GitHub 中有 MT 管理器 的拓展
VS Code 的拓展则可以直接在应用商店中搜索到