LogoNGA 开发文档

基本用法

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",];<全角分隔符>
<除引号外,其他全角字符均可与半角字符混用>

显而易见,支持 字符串浮点数整数布尔值 以及它们的 列表 的定义,还有 本身的 列表

命名定义

组 = {
  文本 = ""
  文本列表 = [""]
  组列表 = [{}]
}
  • 组名称文本/文本列表/组列表值名称
  • ""/[""]/[{}] 为值,{} 称为组

故基本类型名称为 字符串/文本浮点数整数布尔值 为特殊类型

列表类型即为 字符串列表/文本列表浮点数列表整数列表布尔值列表 以及 组列表

FVV 2 在 FVV 1 的基础上有了大幅度改进,详见此处

值的命名也是没有什么忌口的,但需要注意的是, 如果一个值被命名为纯数字(仅 整数,因为 浮点数 会被分割为双层组), 那么这个值将无法用于赋值,因为没法判断到底给的是值还是值的名称

同样的,还需要注意正常命名不要带 . 啊喂,会被认为是多层组的定义的

值的定义使用 ;换行 进行, 所有的值都可以置于根路径的 {} 里面(放不放都没区别,只不过加一个 {} 看起来类 JSON 一点?), 块注释使用 <>,没有行注释

换行 可以省略掉以下字符:

  • ;: 当定义值时有 换行 则无需在行尾添加 ;
  • ,: 当定义组时有 换行 则无需在两个值之间添加 ,

} 也可省略分隔符

注释是 FVV 一个比较特色的功能,它可以放到任何地方,请看示例:

<注释>{<注释>a<注释>=<注释>1<注释>;<注释>}<注释>

可以非常直接地看出来,注释完全是想怎么写就怎么写,但是它的作用不止于此

介于 =;(或 换行)之间的最后一个注释,将会被认定为是该值的 描述,在解析时将会被留存为该值的附属值

因为 换行 可以替代 ;描述 又是和定义一样通过 换行/; 来解析的, 所以 描述 不可以写到定义的下一行

为了让 FWW 有更好的开发体验,组列表 及其子值的 描述 是放在前面的

描述 是支持赋值的,也就是说任意字符串都有可能赋值到 描述 上,只要 描述 与该字符串的值名称相同

所以在实践中,建议在编写 描述 时,在开头加一个 - ,从而实现类似于 <- 这是注释> 的效果, 如果字体支持,<- 会变成一个好看的箭头,这样既不会与赋值机制有冲突,还可以提升观感

代码层使用方法

仅支持 UTF-8 文本(或 UTF-8 with BOM),\r/\n/\r\n 文本均可正常解析

引入依赖

在项目中添加文件后,直接 #include 即可:

#include "fvv.hh"

Pub 发布页面

Dart API 文档

先添加依赖:

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 1.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 上以及 DartKotlin 上,在一起的语言开发体验较为相似

解析

由于 Go 的语言特性,其函数名称不得不大写

由于 DartGo 不支持函数重载,为了保证长期维护的可能性,额外添加了 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 换行,默认 \n
  • UseCR: 使用 \r 换行,默认同上
  • UseSpace2: 使用 2空格 作为 缩进 ,默认是 \t
  • UseSpace4: 使用 4空格 作为 缩进
  • IntBinary: 输出整数为 二进制 格式,默认是 十进制
  • IntOctal: 输出整数为 八进制 格式,默认同上
  • IntHex: 输出整数为 十六进制 格式,默认同上
  • DigitSep3: 输出 十进制整数浮点数的整数部分 时每 3 个数字插入一个分隔符
  • DigitSep4: 同上,每 4 个数字插入一个分隔符
  • UseColon: 使用 冒号 而非默认的 等号 作为定义符
  • FullWidth: 将部分符号改为 全角
  • KeepListSingle: 强制 列表 保持为一行(默认长度达到 16 的内容达到 6 则一行一个)
  • ForceUseSeparator: 强制添加 分隔符(最小化时无效)
  • RawMultilineString: 当 字符串 有多行时改为 原始多行缩进字符串(最小化时无效)
  • NoDescs: 移除所有 描述(在 FWW 风格 下对 组列表 无效)
  • NoLinks: 移除所有 链接
  • FlattenPaths: 递归展平只有 1 个值的
  • FWWStyle: FWW 风格,前置 描述

各个语言的格式化选项位置如下:

  • C++: FVV::FormatOpt
  • Dart: FormatOpt
  • Go: 以 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 的语法可用性太低了,便又封装了 IsNodesEmptyIsNodesNotEmpty 来判断子项是否为空, 但使用前最好还是先判断一下值本身是否为 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/[]int64
  • Kotlin: 在存储与判断时均有 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)
  • stringboolsintsdoublesstringsfvvvs(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 的拓展则可以直接在应用商店中搜索到

在此页面...