给dotnet core项目设置nuget国内镜像源

写了个用.Net Core的小工具,部署时发现NuGet被墙,所以本文记录了下修改国内镜像的方法以及可用镜像.

azure在国内有个NuGet的mirror镜像,可以修改NuGet设置来使用.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="cdn.azure.cn" value="https://nuget.cdn.azure.cn/v3/index.json" protocolVersion="3"/>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
</packageSources>
<packageRestore>
<add key="enabled" value="True" />
<add key="automatic" value="True" />
</packageRestore>
<bindingRedirects>
<add key="skip" value="False" />
</bindingRedirects>
</configuration>

用以上内容修改或者替换Linux中的~/.nuget/NuGet/NuGet.Config或者Windows中的%AppData%\NuGet\NuGet.Config路径中的文件,如果用docker发布的在Dockerfile中自行修改即可.

一个简单修改NuGet.Config文件的脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

#!/bin/bash
#cat ./nuget.config > ~/.nuget/NuGet/NuGet.Config
cat > ~/.nuget/NuGet/NuGet.Config <<EOF
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="cdn.azure.cn" value="https://nuget.cdn.azure.cn/v3/index.json" protocolVersion="3"/>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
</packageSources>
<packageRestore>
<add key="enabled" value="True" />
<add key="automatic" value="True" />
</packageRestore>
<bindingRedirects>
<add key="skip" value="False" />
</bindingRedirects>
</configuration>
EOF

本文参考了 https://www.cnblogs.com/cmt/p/nuget-mirror.html

一个关系数据库LevelSQL的设计和实现

前些天过年疫情在家时为了学习和复习数据库知识,实现了一个兼容MySQL的关系数据库LevelSQL,存储层可以基于磁盘文件或者LevelDB或者对象存储OSS或者Redis等中.

主要特性有:

  • 一个查询计算引擎(SQL和执行计划等)
  • 从存储引擎中分离出来的索引(目前索引只支持B+树)
  • 统一的存储层接口以及基于磁盘文件或者LevelDB或者OSS或者Redis等的具体实现
  • 支持MySQL网络协议(可以使用现有MySQL客户端和驱动使用)

代码

本文主要讲下大概设计和结构

为了支持未来支持分布式架构,所以一开始就把查询引擎和存储引擎剥离,但是没有像TiDB那样直接用LSM同时做存储和索引,而是做成存储引擎和索引也分开来,通过固定接口使用和传递数据。计算引擎,索引,存储引擎分离的优势是未来存储引擎做成分布式存储,查询引擎使用无状态计算引擎,索引层做到基于存储引擎之上的应用层本身无状态或者伪无状态,则可以比较容易实现分布式数据库,通过简单给计算层或者存储层增加机器扩容

在这个考虑上,代码架构上主要组件如下

主要组件

  • 存储引擎
    • 存储引擎接口
    • 存储引擎实现
  • 索引
  • 查询引擎
    • SQL解析
    • 执行计划生成
    • 执行计划优化
    • 计划执行器
  • MySQL网络协议

请求处理流程

以insert插入语句和select查询语句为例进行说明

select语句

server端通过MySQL网络协议接收到SQL语句后,先SQL解析器把SQL语句转换成抽象语法树AST,然后通过visitor模式把SQL AST转换成初始的执行计划树,执行计划树是一个由若干执行计划算子构成的树状结构,比如

1
2
3
4
5
select sum(employee.age)
from employee join country on employee.country_id = country.id
where age > 18
order by age asc
limit 10

构造出来的逻辑执行计划树大致如下(火山模型下resultSet.next轻轻从上往下,查询结果数据流从下往上返回)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
                     aggregate(sum(age))
|
projection(从输入中选择employee.age列)
|
limit
|
order by (employee.age asc)
|
group by (无)
|
filter(age > 18)
|
join
| |
scan table employee/index scan table country/index

逻辑执行计划生成后可以根据需要进行优化,比如使用索引或者联合索引,部分不需要的操作可以不做,不需要的列可以不输出给上层,提前做一些表达式计算等

执行计划生成并优化后,交给计划执行器进行执行,LevelSQL中的计划执行器的实现方式是一个执行计划树启动后,把各算子都作为单独任务交给一个线程池执行,各算子从自己的任务队列中循环检查有没有收到新的FetchTask任务,有收到就执行自身逻辑,向children算子发送子任务,把子任务和处理结果作为自身的任务的一次结果.执行器在顶层算子输出sourceEnd结果或者error或者超时时结束本次计划的执行并把结果输出.

FetchTask是一个可能有三种结果(部分数据, 执行结束sourceEnd, 错误)的future类.

insert语句

收到insert SQL语句后,会找到对应表的所有索引,并分别向聚集索引和二级索引(包括联合索引)分别构造key和数据插入索引中,索引收到插入请求后调用存储引擎的接口把数据写入存储层

存储引擎接口和实现

存储引擎的接口比较简单,因为目前还没加入事务的实现,所以存储引擎接口目前设计为一个类Key-Value的API接口供上层调用.

和普通KV接口区别是Key不是单个字符串或者字节数组,而是一个包含(命名空间 namespace: String, 序号 seq: Int)的元祖,value是普通的字节数组.

1
2
3
4
5
6
7
8
9
10

/** namespace是区分不同命名空间的区分词,比如不同索引,不同表有不同的namespace
* seq是一个顺序增加的整数,一般每次只增加1(或者比较少的数量不要求必须递增1)
*/
data class StoreKey(val namespace: String, val seq: Long)

interface IStore {
fun put(key: StoreKey, value: ByteArray) // 需要在新增数据调用时key.seq逐渐增加
fun get(key: StoreKey): ByteArray?
}

之所以要求 key.seq顺序增加是为了索引层增加新页时key.seq可以比较简单的用新页的编号等提供,存储引擎实现时新增数据有更大序号也更容易实现(最简单的比如磁盘文件则可以简单的追加数据)

具体到实现这个接口的方式上,比如磁盘文件mmap到内存后,各namespace有不同文件,每个文件根据seq转换成所在块的偏移量直接写入即可. 如果用LevelDB/Redis实现,则可以把key整体序列化成字节数组或者字符串去作为键值插入.

查询引擎的设计

执行计划目前使用的火山模型,本文中select语句的执行流程中已经有一个很简单的说明,具体实现另外文章详细再写.

SQL解析

因为SQL语法的简单性,所以没用lex/yacc等工具生成parser,直接基于一边遍历+lookahead的方式手写了个SQL Parser。具体可以看代码中com.zoowii.levelsql.sql.parser包下的代码

MySQL网络协议

MySQL网络协议是一个基于TCP上的应用层协议,客户端和服务端发送的命令和返回都是组织成packet的方式发送, packet中有序号和command type等信息。简单介绍下TCP连接建立后的mysql会话建立阶段(connection phase)和会话建立后的命令阶段(command phase).

1
2
3
4
5
6
7

1. TCP连接建立,开始mysql会话建立截断
2. mysql serverclient发送一个handshake packet
3. mysql client根据收到的handshake packet中信息构造包含authentication等信息的handshake response packet给server
4. mysql server接受到handshake response packet后检查包中的信息,验证成功就发送err packet给客户端并结束连接,否则发送ok packet给客户端然后进入命令阶段
5. mysql client在收到ok packet进入命令阶段后,发送命令给mysql server,比如init db packet, query packet等
6. 以mysql client发送query packet为例,server端从query packet中解析得到SQL语句,并执行,得到结果后发送若干个packets给客户端完成这次查询(之所以返回多个包是因为返回结果的列数,headers的内容, 结束headers部分,返回结果的行数,各行数据,结束eof packet等都分别是一个单独的packet)

bts2.0的智能资产(smartcoin)的机制

bts2.0中的智能资产(smartcoin)可以实现类似bitUSD,bitCNY这类和外界法币相对锚定的功能,主要实现机制是抵押+强制清算提供外部市场消除价差的动力。

bts 2.0的smartcoin。先创建好一种smartcoin,比如bitCNY,外部进行喂价 , 然后任何用户都可以通过抵押bts(比如超过1.75倍的bts)铸币这种智能资产,可以类比bts2.0系统为去中心化的央行,铸币相当于资产负债表扩表,资产(抵押的bts)和负债(发行的bitcny)同时扩张。用户抵押bts铸币,相当于开了一个bitCNY的空单。

bitcny 发行后,相当于一个有面值的债券,面值就是喂价的价格,任意用户可以任意时间强制清算bitcny,兑换成面值的bts,清算中,系统会按bitcny的所有空单中,从抵押率最低的空单还是强制清算,同时减少空单的bitcny负债和减少抵押品Bts。减少的抵押品bts交给清算bitcny的用户,清算是延后24小时并以24小时后的喂价自动执行无法取消的。

这个清算的过程就是bts系统的资产负债表缩表的过程。整个系统的bitcny的资产端(抵押品bts)和负债端(bitcny)同时缩小。因为有这个强制清算的过程,所以bitcny的价格会相对锚定在市场价格,因为如果系统内bitcny相对于bts的价格低于市场价,用户因为有清算bitcny兑换回Bts的选项,就有动力去买入bitcny然后清算,这个市场过程会减少价差,如果Bitcny相当于Bts的价格高于市场价,用户就有动力在交易卖出bts换为bitcny,然后强清bitcny兑换为更多的Bts,这个市场行为也会自然减少价差。 喂价的行为由比较可信的见证人用户执行(见证人得到大量投票,喂价作假确影响他们整体收益),按照共识计算出一个值,但是空单(抵押bts铸币bitcny)是强行平仓是根据喂价决定,而不是根据bts内部市场的买卖决定,当Bts价格剧烈波动时,有可能喂价远高于实际价格导致空单爆仓。

bancor链的智能资产通过多准备金资产,以及允许按照共识自由申购赎回,也提供了给外部市场消除价差的动力(有利可图),不过bancor链的主要目的还是类似ETF给小众代币提供流动性

.Net字节码转换到Lua字节码的实现方式

最近做了个将.Net的字节码文件转换成Lua5.3官方版本字节码的小项目,这样用C#, F#等编程语言编写的代码就可以运行在Lua虚拟机上和Lua互调用了(虽然这种需求不常见,不过Lua虚拟机实现简单方便定制对运行时比较可控也算一个微不足道的优势)。
先不管为什么会有这个奇怪的需求,考虑到将性能差的动态编程语言编译到JVM, CLR等性能好的平台的字节码比较常见,但是反过来将静态编程语言编译到性能差的动态脚本语言的虚拟机上情况比较少见,所以记录这篇文章分享一下。

先简单介绍下.Net和Lua5.3的虚拟机的堆栈结构,因为这两种虚拟机的堆栈结构不一样,字节码指令也差别较大,不能简单对应转换。

.Net字节码的读取可以使用Mono.Cecil库,Lua字节码生成我是写了个assembler来根据伪汇编生成Lua5.3字节码。

.Net虚拟机有Evaluation Stack(指令下文简称eval stack), Call Stack, 参数区,堆,本质是一个基于堆栈的虚拟机
Lua5.3官方的虚拟机有Slots, 堆,本质是一个基于寄存器的虚拟机

首先因为两种虚拟机的堆栈结构都不一样,.Net的字节码指令基于操作栈和内存堆还有eval stack,而Lua的字节码指令都是操作Slots,类似操作一个个寄存器(不是CPU中的寄存器概念)的,所以要实现两种字节码的转换,我采用的方式是目标Lua字节码序列需要实现模拟.Net虚拟机的堆栈结构,下文有具体描述实现方式。

主要实现流程:
1. 实现.Net方法到Lua proto的映射
2. 实现.Net操作指令到Lua指令的映射
3. 实现.Net的控制流指令到Lua指令的映射
4. 实现.Net的函数调用,函数参数,函数调用的返回值到Lua字节码的映射
5. 实现.Net常用函数和类库到Lua的映射(只实现能关联到Lua中的函数和类库)
6. .Net中提供Lua内置库的mock类,从而实现C#写代码,最终编译到Lua字节码后可以使用Lua内置库
7. .Net中提供Lua的table类型等类型的内置库
8. 精简生成的字节码指令,去除因为堆栈结构不一样导致的生成过程中一些无用操作(比如刚压栈就出栈)

将.Net的一个method映射成为Lua中的一个proto,同样有maxstacksize, numOfLocalVariables, 另外有numOfUpvalues。将.Net的非基本对象(数字,布尔类型,字符串等)映射为Lua的table类型对象和proto,.Net中一个有若干方法和属性的对象,映射为Lua中的table和proto, proto中有若干个slots分别通过Lua的closure指令指向不同的其他proto(.Net类型的methods映射的结果),.Net对象的属性放入table中。

然后最关键的是实现.Net的method方法转换到Lua的proto的实现。

对于.Net虚拟机中的eval stack概念,可以通过在目标Lua字节码的proto中头部固定设置一个slot,存储一个table来模拟.Net中的eval stack,另外提供几个固定slot用来存放计算时临时用的对象(比如存储eval stack长度等)

对于.Net中的call stack概念,在目标Lua字节码的proto中专门开辟一段slot区域用来存储,这段slot区域的开始和结束位置,可以根据.Net中method的maxstacksize和指令操作的最大call stack loc计算出来。

然后就是.Net各种指令到Lua指令的翻译了,是一个苦力活,下面简单举例说下一些指令转换的方式

每个proto头部先加入指令 newtable %0 0 0;  %0表示slot 0, @0表示upvalue 0, upvalue的处理有空再说

以下不少翻译后指令结合起来后没必要可以去重精简掉


.Net的IL指令(用一些基本常用指令介绍)                                                   Lua指令(简要描述)

stloc 从eval stack 弹出栈顶数据复制到call stack                 len %1 %0; gettable %(callStackSlotStart+loc) %0 %1; loadnil %3 0; settable %0 %1 %3;
ldloc 从当前函数栈的call stack把某个数据复制到eval stack         len %1 %0; add %1 %1 const 1; settable %0 %1 %(callStackSlotStart+loc)
ldc_i4 加载4字节int常量到eval stack                             loadk %1 const ConstValue; len %2 %0; settable %0 %2 %1
add    消耗eval stack的顶部2个值, 计算结果存入eval stack         len %1 %0; gettable %4 %0 %1; loadnil %3 0; settable %0 %1 %3; len %1 %0; gettable %5 %0 %1; settable %0 %1 %3;
                                                                         add %2 %4 %5; len %1 %0; add %1 %1 const 1; settable %0 %1 %2
call   调用.Net函数                                             先从eval stack取出若干参数放入slot, 然后根据具体情况判断是转换成Lua指令还是全局函数还是某个table的成员函数,
                                                               比如getupval %2 @(被call的func的upvalueIndex) / gettabup %2 @(env的upvalueIndex) const "函数名" 等,
                                                               然后call %(func slot) 参数数量+1 返回值数量+1,如果method有返回值,还需要增加把返回值存入eval stack的指令
br     无条件跳转到目标指令                                      分为跳转到当前指令之前的指令还是之后的指令,如果是之后的指令,需要预先计算出接下来若干.net指令转换到Lua指令后的数量计算偏移量,知道便宜量后就可以在                                                                   proto整体转换完成后在对应位置插入label,本指令转换为jmp $labelName
beq, bgt, bge, blt, ble, bne   比较两个值(eval stacktop-1和top),满足一定条件就jmp到目标指令                  len %1 %0; gettable %arg2 %0 %1; loadnil %3 0; settable %0 %1 %3; len %1 %0; 
                                                                 gettable %arg1 %0 %1; settable %0 %1 %3;lt 0 %arg1 %arg2; jmp 1 $labelOfNextIlInstruction; jmp 1 $targetLabel
brtrue, brfalse 如果eval stack top值是1/0就jmp到目标指令           类似的条件跳转
ret     结束当前函数栈并返回eval stack中的数据,需要根据当前.Net方法的返回类型是否void来判断返回数据
newarr  创建一个空数组放入eval-stack顶                                   newtable替代
newobj  创建一个空的未初始化对象放入eval-stack顶                          newtable替代
dup     把eval stack栈顶元素复制一份到eval-stack顶                    len %1 %0; gettable %2 %0 %1; add %1 %1 const 1; settable %0 %1 %2
cgt, clt, ceq   比较两个值(eval stack top-1 和 top)。如果第一个值大于第二个值(如果是Clt/Clt_Un,则是比较是否小于),则将整数值 1 (int32) 推送到计算堆栈上;反之,将 0 (int32) 推送到计算堆栈上     这里也转换成条件跳转
ldnull 加载null到eval stack
ldstr  加载字符串常量到eval stack
ldarg 从当前函数栈的参数列表中把某个参数复制到eval stack           
等等 

以上描述得虽然还有内容很少,但是足够实现C#的大部分语法对应的字节码转换到Lua5.3字节码了,比如类型,函数定义,函数参数,函数调用和返回值,if, for, while, break, continue, 变量赋值,比较操作符,数值操作,内置函数操作比如字符串连接和print等。以后有空再补充更多细节

Lua的一些坑的记录

Lua是一门很小巧的编程语言,不过使用过程中发下一些容易出现问题的地方,这里记录一下(API正常使用不记录)。记录时使用的版本是官方Lua 5.3.4版本源码.

  • Lua的table区分数组部分和哈希表部分,数组部分索引从1开始,而不是0-based

  • Lua的C API中的lua_isstring和lua_isnumber有点坑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
LUA_API int lua_isnumber (lua_State *L, int idx) {
lua_Number n;
const TValue *o = index2addr(L, idx);
return tonumber(o, &n); // 坑,会执行转换成number类型改动栈
}

LUA_API int lua_isstring (lua_State *L, int idx) {
const TValue *o = index2addr(L, idx);
// 坑,这里逻辑是看能否转换成string,之后如果调用lua_tostring会改动栈中值,在遍历table判断key的时候就悲催了
return (ttisstring(o) || cvt2str(o));
}

-- 容易导致错误的使用比如
lua_State *L = luaL_newstate();
luaL_dostring(L, "a={};a[2]=123");
lua_getglobal(L, "a");

lua_pushnil(L);
while(lua_next(L, -2))
{
auto is_string_key = lua_isstring(L, -2);
if (lua_isstring(L, -2))
{
printf("%s=", luaL_checkstring(L, -2));
}
else if(lua_isinteger(L, -2))
{
printf("%d=", luaL_checkinteger(L, -2));
}
printf("%d\n", luaL_checkinteger(L, -1));
lua_pop(L, 1);
}

lua_close(L);

-- 以上这段代码会进程崩溃,因为错误的对int值进行了luaL_checkstring(或lua_tostring)导致当前lua虚拟堆栈被改写,然后lua_next的时候会找不到正确的key,然后报错崩溃
-- 而下面这样写就正常了
lua_State *L = luaL_newstate();

luaL_dostring(L, "a={};a[2]=123");
lua_getglobal(L, "a");

lua_pushnil(L);
while(lua_next(L, -2))
{
auto is_string_key = lua_isstring(L, -2);
if(lua_isinteger(L, -2))
{
printf("%d=", luaL_checkinteger(L, -2));
}
else if (lua_isstring(L, -2))
{
printf("%s=", luaL_checkstring(L, -2));
}
printf("%d\n", luaL_checkinteger(L, -1));
lua_pop(L, 1);
}

lua_close(L);

-- lua_isnumber也有类似问题,实现中是调用lua_tonumber看能否转换成number类型来判断,而这会改动lua虚拟堆栈结构,在遍历lua table的时候有问题
  • Lua的C API中的lua_type函数,得到的类型可以用来判断布尔,nil,字符串类型,比如lua_type(L, -1) == LUA_TSTRING,但是不能用来直接判断整数,不能lua_type(L, -1) == LUA_TNUMINT 因为Lua 5.3中虽然区分了int和number类型,把整数和浮点数区分出来了,但是lua_type的返回类型都是LUA_TNUMBER,LUA_TNUMINT和LUA_TNUMFLT都是LUA_TNUMBER进行偏移后的结果。需要判断类型的时候整数需要使用lua_isinteger(L,-1),浮点数需要使用lua_type(L, -1) == LUA_TNUMBER

  • Lua的一些函数比如#,ipairs, table中一些函数,都是只对Lua table中的数组部分起效果,建议不要对同一个lua table同时使用数组部分和哈希表部分,可能容易出错。我们的做法是对Lua语言做了修改,增加了可选的类型声明和编译期静态类型系统并区分了Array和Map类型,减少混用导致的问题。

  • Lua中的数学操作符,必须参数都是数字,注意使用时候不要错误使用了类型,包括nil也不行,比如+/-/*//还有其他一些数学操作符

  • 注意区分lua_pcall/lua_pcallk和lua_call/lua_callk的使用区分,前者是runproteced模式下运行,也就是会捕获运行时异常并处理,不对使用者再直接抛出异常崩溃.但是也不要乱用lua_pcall,只在最外层使用

  • 暂时就想到这些,以前可能也碰到一些其他问题没记录下来,以后我想起或者又碰到了再补充

一个简单的正则表达式和语法分析引擎的JavaScript实现parser.js

前段时间为了克服拖延症,花了几天时间把以前一直想填的一个小坑填了,就是这个parser.js库了 https://github.com/zoowii/parser.js

懒得写,就把readme.md抄一部分过来

Features

  • 实现了一个基于NFA的正则表达式引擎实现(正则表达式字符串的解析使用自身实现的底层API和下面的语法解析引擎来实现的),支持主要正则功能已经分组捕获等
  • 实现了一个语法解析引擎(支持左递归定义,语法定义简单)
  • 提供一个整合了词法分析和语法分析的API,方便使用,可以直接生成最终满足要求的抽象语法树(直接生成最初定义规则对应的抽象语法树,而不是解析过程中的中间产物)
  • 为了性能考虑,另外提供了一个使用内置正则引擎的快速词法分析实现,并且尽量兼容自己实现的正则API,从而可以在性能有问题时切换
  • 直接浏览器和Node.js环境,且不依赖任何第三方库

Demo

  • 正则表达式引擎demo
1
2
3
4
5
6
7
8
9
10
11
12
13
console.log('-----test regex string reader=====');
var expr1 = RegexReader.read("\"(a{3,})(b+)(([c\\s\\.\\d\\\\\\+\\u1234])*)");
expr1.build();
console.log('regex build done');
var r1 = expr1.match("aabbbc 123+556end");
var r2 = expr1.match("\"aaaabbbc 123+556");
// var expr2 = RegexReader.read("abc");
console.log(expr1.toString());
console.log(r1.toString());
console.log(r2.toString());
assert.equal(false, r1.matched);
assert.equal(true, r2.matched);
console.log('-----end test regex string reader-----');
  • 语法分析demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
console.log('-----test parser api-----');
parser.clearVarCache();
var syntaxParserAndTokener = parser.buildSyntaxTreeParser(V('json'), [
[V('bool'),
"(?:true|false)\\s*"],
[V('number'),
"(?:[+-]?(?:(0x[0-9a-fA-F]+|0[0-7]+)|((?:[0-9]+(?:\\.[0-9]*)?|\\.[0-9]+)(?:[eE][+-]?[0-9]+)?|NaN|Infinity)))\\s*"],
[V('string'),
"(?:(?:\"((?:\\.|[^\"])*)\"|'((?:\\.|[^'])*)'))\\s*"],
[V('name'),
V('string')],
[V('value'),
V('bool'), V('number'), V('string'), V('json-object'), V('json-array')],
[V('json-object-pair'),
[V('name'), ":\\s*", V('value')]],
[V('json-object-pairs'),
V('json-object-pair'),
[V('json-object-pair'), ",\\s*", V('json-object-pairs')]
],
[V('json-object'),
["\\{\\s*", V('json-object-pairs'), "\\}\\s*"]],
[V('values'),
V('value'),
[V('value'), ",\\s*", V('values')]],
[V('json-array'),
["\\[\\s*", V('values'), "]\\s*"]],
[V('json'),
V('json-object'), V('json-array'), [V("\\s+"), V('json')]]
]);
var jsonParser = syntaxParserAndTokener.parser;
var tokenPatterns = syntaxParserAndTokener.token_patterns;
var text = '{"name": "zoowii", "age": 24, "position": {"country": "China", "city": "Nanjing"}}';
var tokens = parser.generateTokenListUsingInnerRegex(tokenPatterns, text, console.log);
var json = jsonParser.parse(tokens);
console.log(json.toString());
console.log('-----end test parser api-----');

目前支持的正则表达式特性

  • ‘|’
  • ( … ) group
  • \d digit
  • \w alpha or digit
  • \uabcd unicode char support
  • a-b char range
  • [abc] union
  • [^abc] except union
  • abc concat
  • a+ repeat at least one times
  • a* repeat at least zero times
  • a? repeat one or zero times
  • a{m[,n]} repeat at least m times [and at most n times]
  • . any char
  • \s space
  • \ + * { [ ( | \? . - … escape

一个把hibernate/jpa/jdbc封装成类似ActiveRecord风格API的数据库操作库jpa-utils

本博客系统使用Java Web实现,其中数据库层操作虽然直接写SQL或者使用MyBatis比较可控,
但是我对于快糙猛的项目一向懒得慢慢写,所以挺久之前封装过一个类似ActiveRecord的数据库操作库,
是对Hibernate/JPA/jdbc的封装,从而在上层使用时比较方便,又可以随时fallback到使用下层的Hibernate/JPA/jdbc. 项目名叫做jpa-utils

项目地址: [https://github.com/zoowii/jpa-utils](https://github.com/zoowii/jpa-utils)

目前的特性包括

  • 底层基于JPA或者Hibernate的Session/SessionFactory,基于HQL/SQL,比自己再轮一个类HQL稳定
  • 也提供直接基于jdbc的支持,从而可以不依赖Hibernate等ORM,也方便直接和jdbc Connection, MyBatis, DbUtils, 数据库连接池等库直接集成使用
  • 直接基于jdbc Connection的话,ORM映射部分目前只支持MySQL和H2数据库,其他数据库待支持
  • 可以自动从JPA配置创建管理session,也可以手动指定EntityManagerFactory/EntityManager/SessionFactory(hibernate)/Session(hibernate)来构造jpa-utils中的Session来使用,还可以直接从jdbc Connection构造Session
  • 提供类似ActiveRecord的使用方便友好的API,特别是查询API
  • 查询的核心Finder类可以单独使用,直接使用到现有的使用JPA或Hibernate的代码中,只需要根据现有EntityManager/Session(hibernate)构造一个jpa-utils的session,然后使用Finder类来查询就好了
  • 支持类似MyBatis的执行编程式XML中的SQL(TODO)

因为没有发布到公网maven repo,所以如果要用,要么maven install,要么发布到自己meven repo私服,要么mvn package成jar后自己使用

使用DEMO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// maven
!!! First deploy it to you local maven nexus, then.
<dependency>
<groupId>com.zoowii</groupId>
<artifactId>jpa-utils</artifactId>
<version>x.y.z</version>
</dependency>

// create
Session session = EntitySession.currentSession(); // or Session.getSession(persistentUnitName);
session.begin();
try {
for (int i = 0; i < 10; ++i) {
Employee employee = new Employee();
employee.setName("employee_" + StringUtil.randomString(10));
employee.setAge(new Random().nextInt(100));
employee.save(); // or employee.save(session);
logger.info("new employee " + employee.getId());
}
session.commit();
} catch (Exception e) {
e.printStackTrace();
session.rollback();
}

// query
Session session = Session.currentSession();
session.begin();
try {
Query<Employee> query = Employee.find.where().gt("age", 50);
query = query.limit(8);
List<Employee> employees = query.all(); // or query.all(session);
for (int i = 0; i < employees.size(); ++i) {
Employee employee = employees.get(i);
logger.info((i + 1) + ". employee " + employee.getId());
}
logger.info("total: " + query.count()); // or query.count(session);
session.commit();
} catch (Exception e) {
e.printStackTrace();
session.rollback();
}

程序语言的词法作用域中同名变量的rename

TODO

关于在实现程序语言中,如何实现对不同词法作用域中同名变量的rename操作,从而下一步操作更加方便.

比如把如下代码

1
2
3
4
5
6
function func1() {
var name = "zoowii";
function func2(name) {
return "Hello, " + name;
}
}

自动修改为

1
2
3
4
5
6
function func1() {
var name = "zoowii";
function func2(name_unique_2) {
return "Hello, " + name_unique_2;
}
}

先记下来,有空再写

一个js的小玩具ring.js,类似Ring风格的一个web-framework

前段时间一时兴起,弄了个新玩具,ring.js, 项目地址https://github.com/zoowii/ring.js

这是一个非常简陋的类似于Clojure的Ring库的node.js框架

使用Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function helloHandler(req, name) {
return 'hello, ' + name;
}

function fileTestHandler(req, path) {
return new ring.FileStream('/Users/zoowii/SystemVersion.plist');
}

app = ringMiddlewares.routesMiddleware(app, defroutes(
GET('/', 'index', 'index'),
GET('/hello/:name', helloHandler),
GET("/test/:id/update", "test-handler", "test"),
GET('/test/file/:*path', fileTestHandler, 'file-test'),
context("/user", [
GET("/:id/view/:project/:*path", "view_user_handler", "view_user"),
POST("/:id/view/:project/:*path", "update_user_handler", "update_user")
]),
ANY("/:*path", '404-handler', '404')
));
app = ringMiddlewares.resourceMiddleware(app, '/static', __dirname);
var server = httpAdapter(app, {
port: 3000
});
server.start(function () {
console.log('listening at http://127.0.0.1:3000');
});

ring.js概念

ring.js包括handler, middleware, adapter, request, response这些对象的概念

handler

handler是一个接受request返回response的函数,整个ring.js的webapp就是一个handler

middleware

middleware是一个接受若干个handlers和options配置的函数,返回一个新的handler

adapter

adapter封装底层http库等协议,在adapter上可以运行ring.js的webapp,也就是handler. adapter是一个接受一个handler(暴露的ring.js webapp)和options的函数,options可以包括address, port等信息 当一个新请求到来时,adapter封装这个请求成一个request,交给参数中的handler,然后把返回的response作为http回复

request

request代表一个http请求,是一个javascript object,结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
server_port: (required, int),
upgrade: (required, boolean), // true/false
server_name: (required, string),
remote_addr: (required, string), // 请求发送方或者最后一层代理的地址
uri: (required, string), // 请求uri, 以'/'开头
query_string: (optional, map),
scheme: (required, string), // 传输协议,目前支持http和https, 转换成小写
http_version: (optional, string), // http协议版本,比如1.1
request_method: (required, string), // 请求方法, 转换成小写,包括get, post, head, put, delete等
content_type: (optional, string),
content_length: (optional, int),
character_encoding: (optional, string), // 用来转换request body的编码
ssl_client_cert: (optional, byte[]), // 客户端SSL证书
headers: (required, map), // http头信息, header name都转换成小写
body: (optional, stream) // http请求body
}

response

response代表一个http回复,是一个javascript object,或者是一个future对象,如果response是一个map,结构如下:

1
2
3
4
5
{
status: (required, int), // >= 100, http status code
headers: (required, map), // http回复的头信息
body: (optional, string/其他对象的array/iterator/file/stream/上面对象的future, // http回复的body
}

如果response是一个future对象,则监听这个future对象的data事件和end事件,获取到map形式的response chunk并输出