duangsuse::Echo
考虑一下我们的『简单形式』代码: (httpMethod '@')? interfaceName '(' Args ')' ('->' ReturnType)? '=' UrlPath 而 Args 形似 itself repeatItSelfWithPrefix(','): name '?'? OptionVals? 而 OptionVals 形似 '{' val (',' val)* '}' 而 ReturnType 形似 (arrray|object) ':' AtomicObject…
总结一个 PEG 文法:
好了,那么修改完上面两个,就能拯救 GeekApk 目前 API 文档的烂摊子... 😕
File := Interface*
Interface :=
(httpMethod '@')? interfaceName '(' Arg* ')' ('->' ReturnType)?
'=' urlPath
Arg := argName '?'? OptionVals? (',' Args?)
OptionVals := '{' possibleValue (',' possibleValue)* '}'
ReturnType
:= (array|object) ':' Atom
| '[' propertyName (',' propertyName) ']'
| Atom
Atom := (ReturnType|number|string|entityName)
... 等等,我们还没有支持这些语法(其实目标是拿来生成 Boilerplate code 的,不过要这些也有用)index() -> object<category,object<operation,linkTemplate>>====
version() -> plain
upTime() -> (string|number):datetime上面两个还是不要支持,要支持在 Kotlin 项目上可能也没有多大价值
detailedInfo() -> object:<prop,desc>
PUT@transferAppOwner(aid,uid) -> object:[aid,old,new]这两个是自己有问题,也不支持
DELETE@deleteAppUpdate(aid,rev) -> object:appUpdate
listMetaUser(sort?{created,followers},sliceFrom?,sliceTo?) -> array:object:user
这个也是好了,那么修改完上面两个,就能拯救 GeekApk 目前 API 文档的烂摊子... 😕
GeekSpec := ApiSpec*
ApiSpec :=
(httpMethod '@')? interfaceName '(' Arg* ')'
('->' (ReturnType|Dict))? '=' urlPath
Arg := argName '?'? OptionVals? (',' Arg?)
OptionVals := '{' possibleValue (',' possibleValue)* '}'
ReturnType
:= (array|object) ':' Atom | Atom
Atom := (boolean|number|string|datetime|plain|entityName|)
Dict := '[' DictItem (',' DictItem)* ']'
DictItem
:= ReturnType
| '$' fieldName ':' ReturnType
https://pegjs.org/online
的编辑器没有自动保存,我写了半天的内容全丢了。
真棒。
一方面有我自己没有理清递归思路,还有莫名其妙使用递归解析规则的原因,我开始根本没想到自己应该在写逻辑的时候好好想想这是递归的解析规则
另一方面,PEG.js 自己就有问题,我绑定给它的参数它有时候都弄不正确,? 语义根本行为莫名其妙,真是令人琢磨不透,害得我查了半天还找不到解决方案,最后自己勉强弄了一个能用的 workaround
ApiSpec :=
(httpMethod '@')? interfaceName '(' Arg* ')'
('->' (ReturnType|Dict))? '=' urlPath
Arg := argName '?'? OptionVals? (',' Arg?)
OptionVals := '{' possibleValue (',' possibleValue)* '}'
ReturnType
:= (array|object) ':' Atom | Atom
Atom := (boolean|number|string|datetime|plain|entityName|)
Dict := '[' DictItem (',' DictItem)* ']'
DictItem
:= ReturnType
| '$' fieldName ':' ReturnType
https://pegjs.org/online
的编辑器没有自动保存,我写了半天的内容全丢了。
真棒。
一方面有我自己没有理清递归思路,还有莫名其妙使用递归解析规则的原因,我开始根本没想到自己应该在写逻辑的时候好好想想这是递归的解析规则
另一方面,PEG.js 自己就有问题,我绑定给它的参数它有时候都弄不正确,? 语义根本行为莫名其妙,真是令人琢磨不透,害得我查了半天还找不到解决方案,最后自己勉强弄了一个能用的 workaround
pegjs.org
Online Version » PEG.js – Parser Generator for JavaScript
PEG.js is a parser generator for JavaScript based on the parsing expression grammar formalism.
duangsuse::Echo
GeekSpec := ApiSpec* ApiSpec := (httpMethod '@')? interfaceName '(' Arg* ')' ('->' (ReturnType|Dict))? '=' urlPath Arg := argName '?'? OptionVals? (',' Arg?) OptionVals := '{' possibleValue (',' possibleValue)* '}' ReturnType := (array|object) ':'…
递归的规则能不要写就不要写
上面那个
Args := argName '?'? OptionVals? (',' Args)?
就应该写成 Repeat 组合子的形式,一上来就递归很难受,免得麻烦,要知道 PEG.js 那个鸡肋破真参数绑定工作下,即使好好分析了也未必能做到解决问题... 难受的很
上面那个
Args := argName '?'? OptionVals? (',' Args)?
就应该写成 Repeat 组合子的形式,一上来就递归很难受,免得麻烦,要知道 PEG.js 那个鸡肋破真参数绑定工作下,即使好好分析了也未必能做到解决问题... 难受的很
{
let filterIsNotArray = (xs => xs.filter(x => !Array.isArray(x)));
let isNotUndef = (s => s != 'undefined');
let head = xs => xs[0]
}
GeekSpec = specs:(_ ApiSpec _)* { return specs.map(x => head(filterIsNotArray(x))); }
entityName = letter+
interfaceName = letter+
urlPath "url letters" = (letter / [:/.{}?&=%])+
argName = letter+
possibleValue = letter+
fieldName = letter+
httpMethod
= "GET" / "POST"
/ "PUT" / "DELETE"
/ "PATCH" / "OPTIONS"
/ "HEAD"
ApiSpec =
name:interfaceName _ '(' _ a:Arg? as:(_ "," _ Arg)* _ ')'
_ rtxd:('->' _ (ReturnType / Dict))? _ '=' _ url:urlPath {
return {
method: "GET",
name: name.join(''),
args: a == null ? [] : [a].concat(as.map(ra => ra[3])),
return : isNotUndef(typeof rtxd) ? (rtxd == null ? null : rtxd[2]) : null,
url: url.join('')
};
}
/
method:httpMethod '@' name:interfaceName _ '(' _ a:Arg? as:(_ "," _ Arg)* _ ')'
_ rtxd:('->' _ (ReturnType / Dict))? _ '=' _ url:urlPath {
return {
method: method,
name: name.join(''),
args: a == null ? [] : [a].concat(as.map(ra => ra[3])),
return : isNotUndef(typeof rtxd) ? rtxd[2] : null,
url: url.join('')
};
}
Arg = name:argName _ q:('?' / "!"?) _ ov:OptionVals? {
return {
name: name.join(''),
required: q != "?",
options: ov
};
}
OptionVals = '{' _ pv:possibleValue? pvs:(_ ',' _ possibleValue)* _ '}' {
return pv == null ? [] : [pv.join('')].concat(pvs.map(rpv => rpv[3].join('')));
}
ReturnType
= axo:("array" / "object") ':' a:Atom { return { type: axo, of: ((typeof a != 'string') ? a.join('') : a) }; }
Atom = "boolean" / "number"
/ "string" / "datetime"
/ "plain" / entityName
Dict = '[' _ di:DictItem? dis:(_ ',' _ DictItem)* _ ']' {
return di == null ? [] : [di].concat(dis.map(rd => rd[3]))
}
DictItem
= ReturnType
/ '$' name:fieldName ':' rt:ReturnType {
return { name: name.join(''), type: rt };
}
_ "whitespace"
= [ \t\n\r]*
letter "letter"
= [A-Za-z0-9_\-]
大功告成(其实一点都不,而且某非常智障的 duangsuse 没有用到递归(因为这个玩意开始的时候我输入的数据比较多,分析起来当场爆炸留下心理阴影,列表处理方法我又到处乱用 e.g. splice, reverse, concat 却没有想到 map...),非常不爽) #Parser #PL管他呢反之先火速处理(
duangsuse::Echo
{ let filterIsNotArray = (xs => xs.filter(x => !Array.isArray(x))); let isNotUndef = (s => s != 'undefined'); let head = xs => xs[0] } GeekSpec = specs:(_ ApiSpec _)* { return specs.map(x => head(filterIsNotArray(x))); } entityName = letter+ interfaceName…
This media is not supported in your browser
VIEW IN TELEGRAM
duangsuse::Echo
{ let filterIsNotArray = (xs => xs.filter(x => !Array.isArray(x))); let isNotUndef = (s => s != 'undefined'); let head = xs => xs[0] } GeekSpec = specs:(_ ApiSpec _)* { return specs.map(x => head(filterIsNotArray(x))); } entityName = letter+ interfaceName…
简单点来说,GeekSpec DSL 使用类似如下语法:
GET@methodName(args) = urlTemplate
如果 使用的 HTTP 动词是 GET,那 GET@ 可以省掉不写
args:
argName! argName?
如果不写 ? 或者 !,默认 ?(可选参数)
后面还可以跟 {} 包含参数值选项
返回值:符合 JSON 规范,开头必须是 object: 或者 array:,不支持递归类型(比如 array:array:string)
后面可以跟 boolean/number/string/datetime/plain/entityName(随便什么其他的东西)
返回值也可以是 Dict:
[]
dict 组织一打不同的返回值,其实是 object(不关心属性名),可以为他们命名
[$name:type]
GET@methodName(args) = urlTemplate
如果 使用的 HTTP 动词是 GET,那 GET@ 可以省掉不写
args:
argName! argName?
如果不写 ? 或者 !,默认 ?(可选参数)
后面还可以跟 {} 包含参数值选项
返回值:符合 JSON 规范,开头必须是 object: 或者 array:,不支持递归类型(比如 array:array:string)
后面可以跟 boolean/number/string/datetime/plain/entityName(随便什么其他的东西)
返回值也可以是 Dict:
[]
dict 组织一打不同的返回值,其实是 object(不关心属性名),可以为他们命名
[$name:type]
duangsuse::Echo
geekspec_parser.js
GeekApk 的 v1b API 规范正在使用 GeekSpec DSL 语言重写
duangsuse::Echo
好像能用
目前勉强能用的 GeekSpec DSL 将能够解决不断手工重复代码的问题,起码可以大大提升以后的接口开发效率
当然你也可以喷我不上 RESTful 所以才有这些鬼事情,不过我觉得没什么。
当然你也可以喷我不上 RESTful 所以才有这些鬼事情,不过我觉得没什么。
duangsuse::Echo
GeekApk 的 v1b API 规范正在使用 GeekSpec DSL 语言重写
https://github.com/duangsuse/GeekApk/blob/add-controller-template/code_writer.js
现在已经开始使用 Code Writer JavaScript 脚本自动生成测试了,明天会尝试使用它生成 Timelinecontroller
GET user/{id}/checkHash: checkHash(id-path:UserId!,hash:String!)
Returning object of string
GET user/all: listUser(sort:String?,sliceFrom:UserSize?,sliceTo:UserSize?)
Returning array of GeekUser
所作额外的扩展是一些内部类型信息上的,这样就可以用它来生成 Kotlin 控制器接口,只需要简单调用一下
所有参数名都可以额外有 :TypeName 类型信息,optional 的参数(?参数)自动适配 Kotlin 可空类型系统
参数名也可以额外有 - 位置信息,目前可选项为 body 或 path,对应 Spring Controller Binding 的 @RequestBody 和 @PathVariable
目前的生成效果(未来将被替换为生成 Kotlin 代码)
现在已经开始使用 Code Writer JavaScript 脚本自动生成测试了,明天会尝试使用它生成 Timelinecontroller
GET user/{id}/checkHash: checkHash(id-path:UserId!,hash:String!)
Returning object of string
GET user/all: listUser(sort:String?,sliceFrom:UserSize?,sliceTo:UserSize?)
Returning array of GeekUser
所作额外的扩展是一些内部类型信息上的,这样就可以用它来生成 Kotlin 控制器接口,只需要简单调用一下
String.prototype.split 就可以。所有参数名都可以额外有 :TypeName 类型信息,optional 的参数(?参数)自动适配 Kotlin 可空类型系统
参数名也可以额外有 - 位置信息,目前可选项为 body 或 path,对应 Spring Controller Binding 的 @RequestBody 和 @PathVariable
目前的生成效果(未来将被替换为生成 Kotlin 代码)
GitHub
GeekApk/code_writer.js at add-controller-template · duangsuse-valid-projects/GeekApk
GeekApk, the dying SpringBoot(a.k.a. Sping initializr) server for GeekApk(a.k.a 极安) (R - GeekApk/code_writer.js at add-controller-template · duangsuse-valid-projects/GeekApk
目前,GeekSpec 的 Kotlin/Spring 代码生成器已经可以勉强正常工作,不过不是所有特性都测试到了,目前借助强大的 IDEAc 它的效果很好,一 瞬 生 成了之前我要写到手软的代码,大家可以来体验一下(虽然一点都不 production ready,有点像那种小脚本
duangsuse::Echo
目前,GeekSpec 的 Kotlin/Spring 代码生成器已经可以勉强正常工作,不过不是所有特性都测试到了,目前借助强大的 IDEAc 它的效果很好,一 瞬 生 成了之前我要写到手软的代码,大家可以来体验一下(虽然一点都不 production ready,有点像那种小脚本
目前的版本它的作用就是把这种 GeekSpec 代码:
searchUser(type:String?{username,nickname,bio}, kw-path:String, sort:String?{created,followers}) -> array:GeekUser
= user/search/{kw}
翻译成 Kotlin + Spring Controller bind 的控制器接口@GetMapping("user/search/{kw}")
@ResponseBody
fun searchUser(@RequestParam("type") type: String/* Maybe username or nickname or bio */, @PathVariable("kw") kw: String, @RequestParam("sort") sort: String/* Maybe created or followers */): List<GeekUser> {
TODO()
}
目前仍然有不少小瑕疵正在修改
duangsuse::Echo
目前,GeekSpec 的 Kotlin/Spring 代码生成器已经可以勉强正常工作,不过不是所有特性都测试到了,目前借助强大的 IDEAc 它的效果很好,一 瞬 生 成了之前我要写到手软的代码,大家可以来体验一下(虽然一点都不 production ready,有点像那种小脚本
GitHub
Tools(GeekSpec): Update spec generator implementation · duangsuse/GeekApk@5841251
- Support for argument{}
- Support for Dict Syntax Map Value type infer
- Support for Dict Syntax Map Value type infer
This media is not supported in your browser
VIEW IN TELEGRAM
(因为我的 Throws 上面已经有了,这里转发个大佬的(
当作自己的祝福喽(因为人类的本质是...
当作自己的祝福喽(因为人类的本质是...