How to Implement a Readline?

Maybe you have been hear about vim, wondering how vim can run in termial which is not same with other stdin/stdout programs. It can capture any keys you press immediately, and control termial to display it in fullscreen.

All right, this article would not teaching you how to write a vim, that’s very hard. Let’s try something easier, aka writing a readline. Everybody knew GNU-readline, which is a very popular line-editing library. We can write a simple line-editing program in golang as follows:

package main

import (
	"bufio"
	"os"
)

func main() {
	buf := bufio.NewReader(os.Stdin)
	for {
		line, err := buf.ReadString('\n')
		if err != nil {
			break
		}
		print(line)
	}
}

Now we can try to enter some letters, press <Enter>, we can see that once you press <Enter>, the screen will print the letters you just type in a new line.

Figure 1

As we know, the terminal will buffer all the data(usually it be will limit in 1024 bytes size) in memory, once we press <Enter>, it will flush those data to stdin of the program. So the first line is the terminal buffer, and the second line is what the program read. When we in terminal buffer, we can delete or insert character.


OK, now we are trying something interesting, type bye<Left>~<Enter>, let’s see what happens.

Figure 2

In termial buffer, we can see that ^[[D represent the <Left> (instead of move the cursor back). Now we have two questions.

  1. Why the output is by~ ?
  2. Why termial represent <Left> as ^[[D ?

The first question is obviously, we know that this program print exactly what we type, so after we type bye<Left>, the cursor is under the character e, so after we type ~, the e character will be overwritten.

To talk about ^[[D, we must talk about ANSI escape code which can enable us to control output options. Usually it starts with <Esc> (maybe that why it called escape code). The sequence that start with <Esc>[ is called CSI. It can be found that <Left> is belong to CSI codes.

Just for mention, terminal represent <Esc> as ^[ (I don’t know why, maybe it has a story -_-). When the program read from stdin, it still is 0x1b.

Code Name
CSI n A CUU – Cursor Up
CSI n B CUD – Cursor Down
CSI n C CUF – Cursor Forward
CSI n D CUB – Cursor Back

n is optional that control n(default 1) cell to move


To be continue

hello

Hello! Who are you?

golang的类型转换的坑和分析

首先,我们来看一个例子

type Stringer interface {
    String() string
}
type String struct {
    data string
}
func (s *String) String() string {
    return s.data
}

上面是类型,然后

func GetString() *String {
    return nil
}
func CheckString(s Stringer) bool {
    return s == nil
}
func main() {
    println(CheckString(GetString()))
}

你们猜答案是什么? 当然,这么诡异的提问方式一看答案就是不合常理的false。 在CheckString里面,s是不等于nil的。 如果你觉得不可思议,那么可以继续看下去了。

需要强调的是,本篇文章仅仅适用于golang。

类型转换

官方文档明确说明了怎么判断类型T是否可以转换成V,正常来说,T要转换成V必需显式声明。

func check64(v int64){}
func check(v int){}
a := 5
b := int64(10)
check64(int64(a))
check(int(b))
check64(a) // panic

不过,如果在满足Assignability的情况下,就可以在没有显式声明的情况下自动进行类型转换 其中,比较容易被忽略的是这两条:

  1. x is the predeclared identifier nil and T is a pointer, function, slice, map, channel, or interface type.
  2. x is an untyped constant representable by a value of type T.
const a = 5
func check64(v int64){}
func checkSlice(v []int){}
check64(a) // -> check64(int64(a))
check64(1024) // -> check64(int64(1024))
checkSlice(nil) // -> checkSlice([]int(nil))

上面的代码是能够正确执行了, 需要注意的是,1024也是属于常量(这里说明了Integer literals是表示了一个integer constant).

分析

当了解之后,再看我刚开始给的例子

func GetString() *String {
    return nil
}
println(interface{}(GetString()))

这里扔出去的实际上是*String(nil), 如果print出来,应该是一个(0x****, 0x0)的值

需要ps的是,print打印一个interface时,第一个值为一个变量的动态类型的指针,第二个值为实际值的指针。这里为了得到动态类型,故意转换成了interface{}

然后,到CheckString,

func CheckString(s Stringer) bool {
    return s == nil
}

这里的nil会被转换成Stringer(nil), 用print大法打印出来,结果是(0x0, 0x0)

是不是感到很奇怪,print打印出来的第一个是类型的指针,为什么是0?首先,一个变量会有一个动态类型和静态类型,如果不是interface的话,静态类型和动态类型是一样的,当interface时,静态类型是interface的值,而动态类型确是他储存的值的实际类型。而print打印的是动态类型,由于这里他的值为0,动态类型也肯定为0.

当这时候执行CheckString(GetString())的时候,这时候s便是*String(nil), 而nil确是<nil>(nil)(空类型的空指针),进行equals判断的时候,根据spec, interface会在dynamic typedynamic value完全一样的时候才返回true。所以这里很明显是false。

如何不踩坑

虽然例子看起来很无厘头,但实际上这个比较容易踩坑的,特别是在想要作死的时候…

type SpecError struct {
    err Error
}
func NewSpecError(err error) *SpecError {
    if err == nil {
        return nil
    }
    return &SpecError{err}
}
func (se *SpecError) Error() string {
    return se.err.Error()
}
func GetObjByID(id string) (*Obj, error) {
    obj, err := xxxxx(id)
    return obj, NewSpecError(err)
    
}
func main(){
    obj, err := GetObjByID("123")
    if err != nil {
        panic(err)
    }
    。。。
}

好吧我承认只要不作死乖乖在GetObjByIDif err != nil不上就不会有这种事了…

思考

type RouterString func() string
type RouterBytes func() []byte
func AddRouter(router interface{}) {
    switch router := router.(type) {
    case RouterString:
        println("string")
    case RouterBytes:
        println("bytes")
    default:
        println("unknown types")
    }
}
func main() {
    AddRouter(func() string{
        println("hello?")
    })
}

这段代码哪里错了?怎么改?

git submodule的理解

一般项目都会引用一些开源的库,或者将一些可以开源的库独立出来的时候,可以通过submodule管理起来。一般来说submodule需要维持一个基线,除非我们确认确实可以升级他。

假设我现在两个项目都使用了一个库, 第一个项目使用了库的一个Post()方法 然后第一个项目停止开发了。 到了第二个项目,由于需求不同,而且恰好一定需要将Post()改成Post(name string),无法做兼容性修改。如果这时候项目一有自己对库依赖的基线,他可以不用使用新版本的库。

添加

git submodule add <repo> <destpath>

然后进入submodule,checkout成我们要的版本。加入git

git add <lib>

这时候已经添加完成了

部署

git submodule init
git submodule update #相当于进入所有库里面进行checkout到记录的版本

正常来说上面两步就完成了

开发库

cd <submodule>
xxxx
git commit -am "xxx"
cd ../
git add <submodule>
git commit -m "update submodule"