日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > php >内容正文

php

gophp解释器_【干货】Gisp 解释器 Golang 辅助开发工具

發(fā)布時間:2023/12/3 php 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 gophp解释器_【干货】Gisp 解释器 Golang 辅助开发工具 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

Gisp 是一個提供給 golang 使用的 Lisp 類 DSL 解釋器。在 Lisp 的基本語法基礎(chǔ)上,針對 go 環(huán)境稍作了一點語法糖。主要目標是提供一個盡可能便于與 golang 互操作的微型DSL工具。

簡介

Gisp用go語言編寫,是一個DSL 解釋器,這個 DSL 基本上就是 LIsp 的基礎(chǔ)語法,針對go程序的互操作需要稍微做了一點擴展。它的主要設(shè)計目標是盡可能方便的在 go 程序中調(diào)用 gisp 解釋器,執(zhí)行 dsl。

我們的項目,目前后臺主要用 golang 開發(fā)。開發(fā)過程中,golang 確實達到了我們期待的易用、易維護。但是有幾個具體的問題阻礙了我們更好的使用它。這是我們開發(fā)一個內(nèi)嵌解釋器的基本動機。我們希望用這種方式提升編程效率,更快的推進工作。

golang 使用過程中的問題

Golang 是一門很好的工程語言,整合了幾十年來工程界一些已被證明行之有效的經(jīng)驗。成為一門非常適合網(wǎng)絡(luò)服務(wù)開發(fā)的后端工程語言。但是 golang 也存在一些具體的問題,影響了我們團隊的工作效率。

在 golang 中,沒有異常拋出和捕獲的機制,通常通過函數(shù)返回多個參數(shù)的方式,在 error 返回值中傳遞錯誤狀態(tài)。這樣的好處是錯誤不中斷程序,對于一些連續(xù)處理的程序邏輯非常方便。但是相應(yīng)的,沒有強制中斷機制,對于一些依賴程序狀態(tài),出錯需要跳出流程,但是不中斷整個程序進程的場合,就無能為力了。典型的,當我們需要組合大量的小函數(shù)調(diào)用的時候,幾乎每一步都要寫一個狀態(tài)判斷。

if err != nil {

return nil, err

}

在類似 parsec 解析這樣密集使用自定義 Parser、用 Bind 組合子傳遞狀態(tài)時,這種固定的錯誤處理代碼可以超過程序代碼行數(shù)的一半以上。這浪費了開發(fā)人員的工作,也影響代碼閱讀,提高了維護難度。

另一個問題是類型推導(dǎo)過于簡單。缺少泛型和 overload 機制。這樣固然學(xué)習(xí)簡單,編譯器的性能和質(zhì)量容易有保障,但是代價是一些編程需求比較難實現(xiàn)。例如我們需要實現(xiàn)帶單位的商用數(shù)據(jù)的統(tǒng)計計算,就要處理非數(shù)值類型的累加。在我們的項目中,我們期望這個邏輯可以在運行期間不修改程序代碼,穩(wěn)定可靠的適應(yīng),這對于golang比較困難。

要在不修改 golang 編譯器和語法的前提下,緩解這些由 golang 語法限制的問題,就要提供一個可以方便調(diào)用的 DSL 環(huán)境。這里我們選擇實現(xiàn)一個基本的 Lisp 解釋器。

安裝和環(huán)境構(gòu)造

gisp依賴 github.com/Dwarfartisan/goparsec 。使用 gisp 前需要安裝 goparsec 和 gisp 。

go get github.com/Dwarfartisan/gisp

go get github.com/Dwarfartisan/goparsec

導(dǎo)入gisp時引用:

import (

"github.com/Dwarfartisan/gisp"

)

上面這個示例代碼只傳入了基本的公理操作。其中包括了 Lisp 七公理中的六個(cons 在這個環(huán)境中沒有實用的價值,直接實現(xiàn)為 append的封裝 concat)。當然我們甚至可以連公理體系都不加入,那時 gisp 仍可以作為一個詞法解析工具使用。

技術(shù)選擇與設(shè)計

選擇 Lisp ,主要是考慮兩個方面。

Lisp 的前端容易實現(xiàn)。之前為了業(yè)務(wù)后臺,我們在 golang 中實現(xiàn)了文本解析工具 parsec 。這里可以復(fù)用。

另一方面,Lisp 的程序即數(shù)據(jù)結(jié)構(gòu),這對于我們處理混合編程非常方便,可以將數(shù)據(jù)和程序調(diào)用在外部組裝后傳入解釋器環(huán)境。

調(diào)用接口

我們?yōu)?gisp 提供兩個執(zhí)行程序的接口,Parse 接受代碼文本:

pi, err := gisp.Parse("box[\"c\"]")

if err != nil {

t.Fatalf("except got pi is 3.14 but error: %v", err)

}

而Eval是傳遞golang對象:

func TestMulAutoOverload(t *testing.T) {

in := Float(30.9)

ratio := Float(0.8)

out := in * ratio

g := NewGisp(map[string]Toolbox{

"axioms": Axiom,

"props": Propositions,

})

g.Defun("*", mrmul())

mulx, ok := g.Lookup("*")

if !ok {

t.Fatalf("except got overloaded function *")

}

ret, err := g.Eval(List{mulx, in, ratio})

if err != nil {

t.Fatalf("except %v * %v is %v but error %v", in, ratio, out, err)

}

if !reflect.DeepEqual(ret, out) {

t.Fatalf("except %v * %v is %v but %v", in, ratio, out, ret)

}

}

看起來我們?nèi)匀灰幚?Parse 和 Eval 中傳遞出來的 error 狀態(tài),但是 gisp 會自動監(jiān)測每一個代碼語句的執(zhí)行結(jié)果,一旦有錯誤就跳出,我們只需要在每次調(diào)用 Parse 或 Eval 后監(jiān)測一次。

上例中的代碼我們后面再做進一步講解。這里我們可以看到,代碼中演示了乘法運算符重載。

gisp 運行機制

1.環(huán)境

首先,這里介紹 Gisp 環(huán)境的概念,Gisp 文本代碼或者 gisp 代碼序列,都執(zhí)行在解釋器對象中,而解釋器解釋代碼序列,需要使用環(huán)境(gisp.Env)。一個 gisp.Env ,需要實現(xiàn) Lookup,Local、Global、Set、Defvar、Defun 方法。

Local 方法查找本地是否有給定命名,這要求實現(xiàn) gisp.Env 時應(yīng)自己實現(xiàn)一個命名管理機制。

Global 方法查找當前環(huán)境的外部環(huán)境是否有給定命名。這要求實現(xiàn) gisp.Env 時應(yīng)實現(xiàn)外部環(huán)境的引用管理。

Set 實現(xiàn)賦值操作,被賦值的命名必須已經(jīng)存在(已定義)。

Defvar 聲明一個變量

Defun 聲明一個函數(shù),因為需要實現(xiàn)函數(shù)重載,這里將函數(shù)和變量命名區(qū)分開。

2.解析和求值

Gisp 遵循一個簡單的機制。通過文本分析過后,代碼解析成一組 gisp 值,到此為止是 Parse 特有的過程。此后進入 Eval,對每一個解析結(jié)果順序求值。

各長度整數(shù)一律解析為 gisp.Int

各長度浮點數(shù)一律解析為 gisp.Float

如果是 Lisp 接口對象,將當前環(huán)境(初始是 gisp 解析器對象)傳入,返回求值結(jié)果。特別的,如果是 List ,首先將第一個元素求值,然后將后續(xù)元素作為參數(shù),嘗試傳遞給第一個元素的求值結(jié)果,將其作為一個函數(shù)執(zhí)行,返回求值結(jié)果。如果解釋器不知道如何調(diào)用這個元素,返回錯誤;如果是 Quote ,返回其包含的元素,這是常見的傳遞數(shù)據(jù)的封裝方法;gisp 不支持標準的 Lisp (a . b)語法,形如 a.b 的表達式被解析為 gisp.Dot 表達式。該表達式求值遵循以下方式:1)首先,將 a 視作一個 Atom,對 a 求值2)如果a的值是 reflect.Value,嘗試獲取名為 b 的 method 或 field3)如果是 map ,嘗試獲取其鍵值(這里的行為類似 javascript)4)如果是gisp模塊(即 toolkit ,其實其內(nèi)容基于 map[string]interface{} ),嘗試獲取對應(yīng)的成員。5)如果不屬于任何 gisp 可解析的類型,返回原值。gisp 將中括號 [] 用于一個語法糖——引入 golang 的索引操作:1)它可以對List、map[string]interface{} 做普通的索引操作;2)對List,支持負索引和切片3)對于其它 reflect.Kind 為 array, slice 和 map 的數(shù)據(jù)結(jié)構(gòu),用反射嘗試進行索引操作,這部分還沒有經(jīng)過充分的測試;未來希望可以支持對嵌套的 List/[]interface{} 和 map[string]interface{} 支持連續(xù)索引操作,這樣可以方便的處理 JSON;3)如果僅給出 [...] ,中括號表達式左邊沒有給出對象,則解析為一個 brackets 函數(shù),它接受一個容器類型作為參數(shù),對其進行前述的索引操作。即 x[...] 等同于 ([...] x)。

如果不屬于任何 gisp 可解析的類型,返回原值。

基本概念和主要數(shù)據(jù)類型

Atom

List

Quote

函子、函數(shù)和 Lambda

內(nèi)置模塊和功能

gisp 公理

公理(axioms)模塊主要用于實現(xiàn) Lisp 語系必須的幾個基本操作。這里沒有完整的實現(xiàn) Lisp 公理,因為 gisp 的語義和實現(xiàn)內(nèi)核都不是基于完整的 Lisp ,而是 golang runtime 。這是出于實用的考慮而非優(yōu)雅。

quote

quote 操作接受任意的數(shù)據(jù),將其封裝為一個 Quote。Quote 在 Eval的時候返回其內(nèi)部保存的數(shù)據(jù)。它常用于 Lisp 的數(shù)據(jù)傳遞,在 gisp 的內(nèi)部也經(jīng)常用個類型直接封裝數(shù)據(jù)用于傳遞。在golang中可以調(diào)用 gisp.Q(x itnerface{}) 函數(shù),得到一個 Quote{x} 。(quote x) 等價于 'x 。

var

var 在最里的一個 Env 中定義一個命名。它可以使用以下幾種形式:

(var x)

(var x::type) 這里需要注意的是,一般來說 Lisp 是弱類型的,而 gisp 其實是強類型的,而且是靜態(tài)類型。不過gisp并不能在解釋器中直接用 gisp 腳本定義新類型,它只能在 golang 環(huán)境中擴展,這是為了讓 gisp 解釋器盡可能保持簡單。

(var x value) 在定義的時候可以給出 x 的值,這里其實內(nèi)部是順序作了 def 和 set 操作

定義x的時候,如果同名的變量或函數(shù)已經(jīng)存在于當前環(huán)境,就會報錯。

set

set 操作比較好理解, (set x value) 就是對x進行賦值,x需要預(yù)先已經(jīng)存在。在 gisp 環(huán)境內(nèi)部,def 會生成一個 gisp.Var 接口的 slot 對象,這個對象內(nèi)部通過反射管理賦值,如果 x 和value 類型不匹配,會導(dǎo)致panic。

equal

euqal 內(nèi)部其實調(diào)用的是 reflect.DeepEqual 。

cond

cond 就是普通的 lisp cond 操作符,相當于 golang 的 value switch case 。gisp 沒有實現(xiàn) type switch。而且目前使用的案例中其實沒有用到過 cond ,這部分沒有經(jīng)過充分的測試。

car

car 取給定 list 的第一個元素,類似于 haskell 的 head 操作。等價于 Lisp 通常意義上的 car 操作符。

cdr

cdr 取 List 除了第一個以外剩下的元素,等同于通常意義的 cdr 操作符,也就是 Haskell 的 tail操作。即 list[1:] 。由于實際使用中還沒有遇到,這里也沒有經(jīng)過充分的測試,從代碼中看對空列表做 cdr 會 panic。

atom

atom 等同于通常意義的 Lisp atom 操作符,如果給定的參數(shù)是 List ,返回false,否則返回true。這個操作符也沒有經(jīng)過充分的測試。

concat

Lisp 的公理 cons ,用于將 head 和 (tail.()) 結(jié)合成一個 list。但是這個功能在 gisp 面向 golang 做互操作的需求前提下沒有存在意義,這里 gisp 實現(xiàn)了一個 concat 操作,內(nèi)部調(diào)用 append,將給定的參數(shù)連接成一個 gisp.List 。

Gisp 定理

定理(propositions)其實是一些基礎(chǔ)操作,主要是比較操作和數(shù)學(xué)運算。這個可以參見其定義代碼:

var Propositions Toolkit = Toolkit{

Meta: map[string]interface{}{

"name": "propositions",

"category": "package",

},

Content: map[string]interface{}{

"lambda": BoxExpr(LambdaExpr),

"let": BoxExpr(LetExpr),

"+": EvalExpr(ParsexExpr(addx)),

"add": EvalExpr(ParsexExpr(addx)),

"-": EvalExpr(ParsexExpr(subx)),

"sub": EvalExpr(ParsexExpr(subx)),

"*": EvalExpr(ParsexExpr(mulx)),

"mul": EvalExpr(ParsexExpr(mulx)),

"/": EvalExpr(ParsexExpr(divx)),

"div": EvalExpr(ParsexExpr(divx)),

"cmp": EvalExpr(cmpExpr),

"less": EvalExpr(lessExpr),

"": EvalExpr(greatExpr),

">?": EvalExpr(gtoExpr),

">=": EvalExpr(geExpr),

">=?": EvalExpr(geoExpr),

"==": EvalExpr(eqsExpr),

"==?": EvalExpr(eqsoExpr),

"!=": EvalExpr(neqsExpr),

"!=?": EvalExpr(neqsoExpr),

},

}

這里有兩個函數(shù)單獨拿出來討論一下,一個是let ,一個是lambda。

let

Let 在 lisp 中構(gòu)造一個封閉的環(huán)境,可以指定若干初始化變量,其作用域僅限于let內(nèi)。

func TestParsecBasic(t *testing.T) {

g := NewGispWith(

map[string]Toolbox{

"axiom": Axiom, "props": Propositions, "time": Time},

map[string]Toolbox{"time": Time, "p": Parsec})

digit := p.Bind(p.Many1(p.Digit), p.ReturnString)

data := "344932454094325"

state := p.MemoryParseState(data)

pre, err := digit(state)

if err != nil {

t.Fatalf("except \"%v\" pass test many1 digit but error:%v", data, err)

}

src := "(let ((st (p.state \"" + data + `")))

(var data ((p.many1 p.digit) st))

(p.s2str data))

`

gre, err := g.Parse(src)

if err != nil {

t.Fatalf("except \"%v\" pass gisp many1 digit but error:%v", src, err)

}

t.Logf("from gisp: %v", gre)

t.Logf("from parsec: %v", pre)

if !reflect.DeepEqual(pre, gre) {

t.Fatalf("except got \"%v\" from gisp equal \"%v\" from parsec", gre, pre)

}

}

通常來講,在實用項目中使用 gisp 解釋器,可以用let得到一個比較干凈和安全的沙箱環(huán)境,用let隔離每一次腳本的運行,使之不會互相干擾。

lambda

lambda 的含義和用法不用太多介紹,就是 Lisp 實現(xiàn)中通常的形式。不過有幾點需要注意:

gisp 中變量可以附帶類型,這是定義函數(shù)重載的方式,但是實踐上我目前為止都是在 go 中構(gòu)造函子。所以這部分沒有經(jīng)過充分測試。原理上,gisp函數(shù)是各種同名但不同類型的 lambda 的集合容器。

lambda 一般來講可以不攜帶類型直接使用,在ginq等工具應(yīng)用場合,lambda往往是用來封裝一段規(guī)則,不需要復(fù)雜的約束。

func TestGinqWhereSelect(t *testing.T) {

data := QL(

L(0, 1, 2, 3, 4, 5),

L(1, 2, 3, 4, 5, 6),

L(2, 3, 4, 5, 6, 7),

L(3, 4, 5, 6, 7, 8))

g := NewGispWith(

map[string]Toolbox{"axiom": Axiom, "props": Propositions, "utils": Utils},

map[string]Toolbox{"time": Time})

g.DefAs("data", data)

ginq, err := g.Parse(`

(ginq

(where (lambda (r) (< 1 r[0])))

(select (fs [1] [2] [4]))

)

`)

if err != nil {

t.Fatalf("except got a ginq query but error %v ", err)

}

re, err := g.Eval(L(ginq, data))

if err != nil {

t.Fatalf("except got columns from data but error %v", err)

}

t.Logf("ginq select got %v", re)

}

常用工具

Parsec

Parsec是我們項目中使用到的重要工具之一。它用于文本和規(guī)則解析。目前 goparsec 的表現(xiàn)尚可,基本完成了預(yù)期目標。但是限于go的語法,有一些地方并不盡如人意。

go 的強類型靜態(tài)檢查,使得 parsec 的 Parser 構(gòu)造能夠基于一個比較嚴謹?shù)妮斎爰s束。但是因為go沒有泛型。文本和[]interface{} 的解析器只能各自實現(xiàn),而在 Haskell 中這些只需要寫一次。

由于沒有泛型,為了讓 goparsec 能夠適用各種不同的解析場合,每個 Parser 的返回值只能寫成 interface{} 。

go 沒有 throw 和 try catch,每訪問一次 state ,都要檢查返回狀態(tài)是否有錯。雖然有大量組合子用于減少這個工作量,例如 Bind_, Bind, ManyTil 都是有力的工具。但是一旦我們需要在狀態(tài)傳遞中加入稍復(fù)雜一點的業(yè)務(wù)規(guī)則,就要實現(xiàn)自己的 Bind Keep 函數(shù)。在這個過程中我們總是要編寫大量的 if err != nil {return nil, err} 。

事實上,在使用 Parsec 的過程中遇到的各種不便,特別是錯誤處理,是我開發(fā) gisp 的最主要動機。

限于 golang 項目在實用中的性能考慮,目前我們?nèi)匀粚?string 和 List 的 Parsec 分別實現(xiàn)為 parsec 和 parsex 。當前只是對 goparsec 的封裝,未來可能會根據(jù) gisp 的實踐經(jīng)驗,向 gisp 化改變。

在 gisp 中調(diào)用 parsec ,最大的好處是省去錯誤監(jiān)測(這個工作由 gisp 自然的接管了),于是就可以用類似haskell 版本的風格去自然的編寫解析過程:

func TestParsecRune2(t *testing.T) {

g := NewGispWith(

map[string]Toolbox{

"axiom": Axiom, "props": Propositions, "time": Time},

map[string]Toolbox{"time": Time, "p": Parsec})

//data := "Here is a Rune : 'a' and a is't a rune. It is a word in sentence."

data := "'a' and a is't a rune. It is a word in sentence."

state := p.MemoryParseState(data)

pre, err := p.Between(p.Rune('\''), p.Rune('\''), p.AnyRune)(state)

if err != nil {

t.Fatalf("except found rune expr from \"%v\" but error:%v", data, err)

}

src := `

(let ((st (p.state "` + data + `")))

((p.rune '\'') st)

(var data (p.anyone st))

((p.rune '\'') st)

data)

`

//fmt.Println(src)

gre, err := g.Parse(src)

if err != nil {

t.Fatalf("except \"%v\" pass gisp '' but error:%v", src, err)

}

t.Logf("from gisp: %v", gre)

t.Logf("from parsec: %v", pre)

if !reflect.DeepEqual(pre, gre) {

t.Fatalf("except got \"%v\" from gisp equal \"%v\" from parsec", gre, pre)

}

}

Ginq

Ginq 模塊也是開發(fā) gisp 的動機之一,我們項目中主要使用的是 go-linq ,這個項目質(zhì)量很高。但是我們需要多步簡單操作的時候,go風格的linq結(jié)構(gòu)仍顯有點笨拙。在 ginq 中可以簡潔很多。

func TestGinqWhereSelect(t *testing.T) {

data := QL(

L(0, 1, 2, 3, 4, 5),

L(1, 2, 3, 4, 5, 6),

L(2, 3, 4, 5, 6, 7),

L(3, 4, 5, 6, 7, 8))

g := NewGispWith(

map[string]Toolbox{"axiom": Axiom, "props": Propositions, "utils": Utils},

map[string]Toolbox{"time": Time})

g.DefAs("data", data)

ginq, err := g.Parse(`

(ginq

(where (lambda (r) (< 1 r[0])))

(select (fs [1] [2] [4]))

)

`)

if err != nil {

t.Fatalf("except got a ginq query but error %v ", err)

}

re, err := g.Eval(L(ginq, data))

if err != nil {

t.Fatalf("except got columns from data but error %v", err)

}

t.Logf("ginq select got %v", re)

}

Ginq 的機制和使用

Ginq 的結(jié)構(gòu)比較特殊,可以把 ginq 函數(shù)看成一個特化的 lambda。它接受一組 ginq 子句,將其串成一個處理序列,生成一個接受 List 參數(shù)的lambda函子,我們稱之為 ginq 查詢。給這個查詢傳入一個 List ,它會順序調(diào)用每個子句,最終返回結(jié)果。

在這個過程中,ginq的一級子句很重要。它們接受List,并將輸出結(jié)果返回到 ginq ,ginq 再將其輸出到下一個子句。目前這里沒有做優(yōu)化,每一步都會生成一個中間 List 。所以使用的時候盡量將 where 這樣的過濾子句放在前面,可以提高效率,節(jié)省內(nèi)存。

select

Select 子句接受一個函數(shù),然后生成一個函數(shù)。新的函數(shù)接受 ginq 傳入的 list,再返回一個list。特別的,我們提供一個 fs (即 fields)函數(shù),這個函數(shù)接受一組函數(shù),生成一個接受單個數(shù)據(jù),返回List 的函數(shù)。這個函數(shù)可以跟 select 組合,形成一個類似 SQL 的列選擇功能。示例如下:

func TestGinqSelectFields(t *testing.T) {

data := QL(

L(0, 1, 2, 3, 4, 5),

L(1, 2, 3, 4, 5, 6),

L(2, 3, 4, 5, 6, 7),

L(3, 4, 5, 6, 7, 8))

g := NewGispWith(

map[string]Toolbox{"axiom": Axiom, "props": Propositions, "utils": Utils},

map[string]Toolbox{"time": Time})

g.DefAs("data", data)

ginq, err := g.Parse(`

(ginq (select (fs [1] [2] [4])))

`)

if err != nil {

t.Fatalf("except got a ginq query but error %v ", err)

}

re, err := g.Eval(L(ginq, data))

if err != nil {

t.Fatalf("except got columns from data but error %v", err)

}

t.Logf("ginq select got %v", re)

}

where

where 子句接受一個判斷函數(shù)為參數(shù),返回一個過濾器。它對傳入的 List 中的元素逐個調(diào)用給定的判斷函數(shù),只有返回值為 true 的才放到輸出結(jié)果中,最終生成一個 List,其中的內(nèi)容是所有通過判斷的數(shù)據(jù)。

func TestGinqWhereSelect(t *testing.T) {

data := QL(

L(0, 1, 2, 3, 4, 5),

L(1, 2, 3, 4, 5, 6),

L(2, 3, 4, 5, 6, 7),

L(3, 4, 5, 6, 7, 8))

g := NewGispWith(

map[string]Toolbox{"axiom": Axiom, "props": Propositions, "utils": Utils},

map[string]Toolbox{"time": Time})

g.DefAs("data", data)

ginq, err := g.Parse(`

(ginq

(where (lambda (r) (< 1 r[0])))

(select (fs [1] [2] [4]))

)

`)

if err != nil {

t.Fatalf("except got a ginq query but error %v ", err)

}

re, err := g.Eval(L(ginq, data))

if err != nil {

t.Fatalf("except got columns from data but error %v", err)

}

t.Logf("ginq select got %v", re)

}

groupby

groupby 執(zhí)行分組統(tǒng)計操作。下例為了更清楚的表現(xiàn)Ginq的串行操作,將groupby中的分組子句拆解成一個新的qinq,其實后面的例子我們會看到更簡潔的寫法。

func TestGinqGroupBy(t *testing.T) {

data := QL(

L(0, 1, 2, 3, 4, 5),

L(1, 2, 3, 4, 5, 6),

L(0, 1, 2, 3, 4, 5),

L(1, 2, 3, 4, 5, 6),

L(2, 3, 4, 5, 6, 7),

L(1, 2, 3, 4, 5, 6),

L(2, 3, 4, 5, 6, 7),

L(3, 4, 5, 6, 7, 8))

g := NewGispWith(

map[string]Toolbox{"axiom": Axiom, "props": Propositions, "utils": Utils},

map[string]Toolbox{"time": Time})

g.DefAs("data", data)

ginq, err := g.Parse(`

(ginq

(groupby [0] (ginq (select [5]) sum))

)

`)

if err != nil {

t.Fatalf("except got a ginq query but error %v ", err)

}

re, err := g.Eval(L(ginq, data))

if err != nil {

t.Fatalf("except got columns from data but error: %v", err)

}

t.Logf("ginq select got %v", re)

}

統(tǒng)計函數(shù)

為了更方便的在ginq中對一個 List 進行統(tǒng)計計算,我們實現(xiàn)了對應(yīng)的一級子句 sums、maxs、mins、avgs。它們接受fs這樣的行處理函數(shù),可以先用行處理函數(shù)對單個數(shù)據(jù)項進行計算后,再做統(tǒng)計。在我們的業(yè)務(wù)中,典型如訂單,每一個消費項先進行結(jié)算,再做總計。

下例演示了groupby、sums、where和中括號表達式的組合。(sums [5]) 隱藏了內(nèi)部的 select fs 和sum等多步操作。

func TestGinqGroupBySumSelectWhere(t *testing.T) {

data := QL(

L(0, 1, 2, 3, 4, 5),

L(1, 2, 3, 4, 5, 6),

L(0, 1, 2, 3, 4, 5),

L(1, 2, 3, 4, 5, 6),

L(2, 3, 4, 5, 6, 7),

L(1, 2, 3, 4, 5, 6),

L(2, 3, 4, 5, 6, 7),

L(3, 4, 5, 6, 7, 8))

g := NewGispWith(

map[string]Toolbox{"axiom": Axiom, "props": Propositions, "utils": Utils},

map[string]Toolbox{"time": Time})

g.DefAs("data", data)

ginq, err := g.Parse(`

(ginq

(groupby [0] (sums [5]))

(where (lambda (x) (> 10 x[1])))

)

`)

if err != nil {

t.Fatalf("except got a ginq query but error %v ", err)

}

re, err := g.Eval(L(ginq, data))

if err != nil {

t.Fatalf("except got group sum from data but error: %v", err)

}

t.Logf("ginq group sum select got %v", re)

}

而單列的“平凡”數(shù)據(jù)集,其實groupby sum過程是這樣的:

func TestGinqGroupBy(t *testing.T) {

data := QL(

L(0, 1, 2, 3, 4, 5),

L(1, 2, 3, 4, 5, 6),

L(0, 1, 2, 3, 4, 5),

L(1, 2, 3, 4, 5, 6),

L(2, 3, 4, 5, 6, 7),

L(1, 2, 3, 4, 5, 6),

L(2, 3, 4, 5, 6, 7),

L(3, 4, 5, 6, 7, 8))

g := NewGispWith(

map[string]Toolbox{"axiom": Axiom, "props": Propositions, "utils": Utils},

map[string]Toolbox{"time": Time})

g.DefAs("data", data)

ginq, err := g.Parse(`

(ginq

(groupby [0] (ginq (select [5]) sum))

)

`)

if err != nil {

t.Fatalf("except got a ginq query but error %v ", err)

}

re, err := g.Eval(L(ginq, data))

if err != nil {

t.Fatalf("except got columns from data but error: %v", err)

}

t.Logf("ginq select got %v", re)

}

這里需要注意的是,sum、max、min、avg、count等函數(shù)不同于 sums 這樣的統(tǒng)計組合子函數(shù),它直接構(gòu)成 List 到 統(tǒng)計結(jié)果的函數(shù),不另組合行處理函數(shù)。

排序

同樣,ginq也提供了處理簡單序列的sort:

func TestGinqSort(t *testing.T) {

data := QL(

L(0, 1, 2, 3, 4, 5),

L(1, 2, 3, 4, 5, 6),

L(0, 1, 2, 3, 4, 2),

L(1, 2, 3, 4, 5, 6),

L(2, 3, 4, 5, 6, 7),

L(1, 2, 3, 4, 5, 3),

L(2, 3, 4, 5, 6, 4),

L(3, 4, 5, 6, 7, 8))

g := NewGispWith(

map[string]Toolbox{"axiom": Axiom, "props": Propositions, "utils": Utils},

map[string]Toolbox{"time": Time})

g.DefAs("data", data)

ginq, err := g.Parse(`

(ginq

(select [4])

sort

)

`)

if err != nil {

t.Fatalf("except got a ginq sort but error %v ", err)

}

re, err := g.Eval(L(ginq, data))

if err != nil {

t.Fatalf("except got ginq sort from data but error: %v", err)

}

t.Logf("ginq sort got %v", re)

}

和基于自定義判斷函數(shù)的 sortby

func TestGinqSortBy(t *testing.T) {

data := QL(

L(0, 1, 2, 3, 4, 5),

L(1, 2, 3, 4, 5, 6),

L(0, 1, 2, 3, 4, 2),

L(1, 2, 3, 4, 5, 6),

L(2, 3, 4, 5, 6, 7),

L(1, 2, 3, 4, 5, 3),

L(2, 3, 4, 5, 6, 4),

L(3, 4, 5, 6, 7, 8))

g := NewGispWith(

map[string]Toolbox{"axiom": Axiom, "props": Propositions, "utils": Utils},

map[string]Toolbox{"time": Time})

g.DefAs("data", data)

ginq, err := g.Parse(`

(ginq

(select (fs [3] [1] [5]))

(sortby (lambda (x y) (< x[2] y[2])))

)

`)

if err != nil {

t.Fatalf("except got a ginq sortby but error %v ", err)

}

re, err := g.Eval(L(ginq, data))

if err != nil {

t.Fatalf("except got ginq sortby from data but error: %v", err)

}

t.Logf("ginq sort got %v", re)

}

各個ginq子句其實可以作為獨立的函數(shù)調(diào)用,使用ginq環(huán)境主要是它會根據(jù)子句判斷求值方式,寫起來可以比較簡潔。提高一致性。我們也可以嘗試定制一些新的ginq子句組合使用。

并發(fā)

我們也提供了 go 關(guān)鍵字和 chan 關(guān)鍵字的封裝,不過目前應(yīng)用中完全沒有用到,所以沒有經(jīng)過測試。

擴展

gisp 的擴展主要是兩部分,一個是通過在 gisp 內(nèi)注冊 go 類型,實現(xiàn)類型擴展。

func TestTypeFound(t *testing.T) {

m := money{9.99, "USD"}

g := NewGisp(map[string]Toolbox{

"axioms": Axiom,

"props": Propositions,

})

g.DefAs("money", reflect.TypeOf(m))

_, err := g.Parse("(var bill::money)")

if err != nil {

t.Fatalf("except define a money var but error: %v", err)

}

g.Setvar("bill", m)

mny, ok := g.Lookup("bill")

if !ok {

t.Fatalf("money var bill as %v not found ", m)

}

if !reflect.DeepEqual(m, mny) {

t.Fatalf("except got money var bill as %v but %v", m, mny)

}

}

上例可以看到,只要定義一個值為reflect.Type 的變量,就可以將其視為一個類型。這里借鑒了一些動態(tài)語言的做法。

或者編寫自己的 gisp.Functor 函子實現(xiàn),作為函數(shù)使用:

type Functor interface {

Task(env Env, args ...interface{}) (Lisp, error)

}

在gisp中調(diào)用函數(shù)時,是從 Task 傳入?yún)?shù),此時函數(shù)可以不執(zhí)行,只是將要執(zhí)行的代碼封裝成一個新的 Lisp 返回,這個設(shè)計是為了兩方面,一個是在出現(xiàn)函數(shù)重載時,先做參數(shù)檢查,有錯誤的話及早返回,也可以在不執(zhí)行代碼的情況下先校驗參數(shù)是否匹配。其次將來實現(xiàn) go 關(guān)鍵字時,可以盡可能在異步任務(wù)之外先排除一些錯誤,然后讓任務(wù)執(zhí)行在無參數(shù)的環(huán)境下,理想情況時這可以是一個封閉的沙箱。

自定義函子通常是若干個組成一個模塊,放進gisp調(diào)用,示例可以參見 axiom.go 等內(nèi)部實現(xiàn)。典型的,Axioms模塊實現(xiàn)的非常簡單,而 Gisp 模塊則非常完整和復(fù)雜。可以看到兩種不同實現(xiàn)方式的利弊。

解釋器

目前默認的解釋器,設(shè)計目標是盡可能輕量。它有buildin的概念,如果將模塊(通常是一個 gisp.Toolkit 實現(xiàn)) 放到 buildin模板,調(diào)用它的成員時不需要 m.fun 這樣的dot 表達式,直接給出命名就可以。否則要指定模塊名。構(gòu)造 Gisp 解釋器對象,有兩個工具方法。NewGisp接受一個map[string]interface{} 作為buildin模塊,而 NewGispWith 則多接受一個ext字典,作為需要顯示引用模塊名的模塊定義。

前面幾個例子中都有引入一些buildin或ext模塊的行為,而下面這個例子甚至沒有引入任何模塊,gisp仍然可以執(zhí)行一些邏輯。

func TestParseFloat(t *testing.T) {

g := NewGisp(map[string]Toolbox{})

gisp := *g

data := "3.14"

ret, err := gisp.Parse(data)

if err != nil {

t.Fatalf("except Float(3.14) but error: %v", err)

}

if ret.(Float) != Float(3.14) {

t.Fatalf("except got Float(3.14) but %v", ret)

}

}

總結(jié)

以上是生活随笔為你收集整理的gophp解释器_【干货】Gisp 解释器 Golang 辅助开发工具的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。