世界上第一个 Go 程序
Andrew Gerrand
2013 年 7 月 18 日
Brad Fitzpatrick 和我 (Andrew Gerrand) 最近开始重组 godoc, 在我看来这是最古老的 Go 程序之一. Robert Griesemer 于 2009 年初开始编写它,而今天我们仍在使用它.
当我 tweeted 时,Dave Cheney 回答了一个 有趣的问题: 最早的 Go 程序是什么?Rob Pike 扒拉许久邮件,在给 Robert 和 Ken Thompson 的旧邮件中找到了它.
接下来是第一个 Go 程序。它是由 Rob 于 2008 年 2 月撰写的,当时团队只有 Rob, Robert 和 Ken. 他们有一个可靠的功能列表 (在 此博客文章 中提到) 和粗略的语言规范. Ken 刚刚完成了 Go 编译器的第一个工作版本 (它不会产生本机代码,而是将 Go 代码转译为 C 以便快速进行原型制作), 现在是时候尝试使用它编写程序了.
Rob 向 "Go team" 发送了邮件:
From: Rob 'Commander' Pike
Date: Wed, Feb 6, 2008 at 3:42 PM
To: Ken Thompson, Robert Griesemer
Subject: slist
it works now.
roro=% a.out
(defn foo (add 12 34))
return: icounter = 4440
roro=%
here's the code.
some ugly hackery to get around the lack of strings.
(程序输出中的 icounter
行是为调试而打印的已执行语句的数量.)
package main
// fake stuff
type char uint8;
// const char TESTSTRING[] = "(defn foo (add 'a 'b)).";
type Atom struct {
string *[100]char;
integer int;
next *Slist; /* in hash bucket */
}
type List struct {
car *Slist;
cdr *Slist;
}
type Slist struct {
isatom bool;
isstring bool;
//union {
atom Atom;
list List;
//} u;
Free method();
Print method();
PrintOne method(doparen bool);
String method(*char <-);
Integer method(int <-);
Car method(*Slist <-);
Cdr method(*Slist <-);
}
method (this *Slist) Car(*Slist <-) {
return this.list.car;
}
method (this *Slist) Cdr(*Slist <-) {
return this.list.cdr;
}
method (this *Slist) String(*[100]char <-) {
return this.atom.string;
}
method (this *Slist) Integer(int <-) {
return this.atom.integer;
}
function OpenFile();
function Parse(*Slist <-);
//Slist* atom(char *s, int i);
var token int;
var peekc int = -1;
var lineno int32 = 1;
var input [100*1000]char;
var inputindex int = 0;
var tokenbuf [100]char;
var EOF int = -1; // BUG should be const
function main(int32 <-) {
var list *Slist;
OpenFile();
for ;; {
list = Parse();
if list == nil {
break;
}
list.Print();
list.Free();
break;
}
return 0;
}
method (slist *Slist) Free(<-) {
if slist == nil {
return;
}
if slist.isatom {
// free(slist.String());
} else {
slist.Car().Free();
slist.Cdr().Free();
}
// free(slist);
}
method (slist *Slist) PrintOne(<- doparen bool) {
if slist == nil {
return;
}
if slist.isatom {
if slist.isstring {
print(slist.String());
} else {
print(slist.Integer());
}
} else {
if doparen {
print("(");
}
slist.Car().PrintOne(true);
if slist.Cdr() != nil {
print(" ");
slist.Cdr().PrintOne(false);
}
if doparen {
print(")");
}
}
}
method (slist *Slist) Print() {
slist.PrintOne(true);
print ".";
}
function Get(int <-) {
var c int;
if peekc >= 0 {
c = peekc;
peekc = -1;
} else {
c = convert(int, input[inputindex]);
inputindex = inputindex + 1; // BUG should be incr one expr
if c == '.' {
lineno = lineno + 1;
}
if c == '.' {
inputindex = inputindex - 1;
c = EOF;
}
}
return c;
}
function WhiteSpace(bool <- c int) {
return c == ' ' || c == '.' || c == '.' || c == '.';
}
function NextToken() {
var i, c int;
var backslash bool;
tokenbuf[0] = '.'; // 清除上一个令牌
c = Get();
while WhiteSpace(c) {
c = Get();
}
switch c {
case EOF:
token = EOF;
case '(':
case ')':
token = c;
break;
case:
for i = 0; i < 100 - 1; { // sizeof tokenbuf - 1
tokenbuf[i] = convert(char, c);
i = i + 1;
c = Get();
if c == EOF {
break;
}
if WhiteSpace(c) || c == ')' {
peekc = c;
break;
}
}
if i >= 100 - 1 { // sizeof tokenbuf - 1
panic "atom too long.";
}
tokenbuf[i] = '.';
if '0' <= tokenbuf[0] && tokenbuf[0] <= '9' {
token = '0';
} else {
token = 'A';
}
}
}
function Expect(<- c int) {
if token != c {
print "parse error: expected ", c, ".";
panic "parse";
}
NextToken();
}
// Parse a non-parenthesized list up to a closing paren or EOF
function ParseList(*Slist <-) {
var slist, retval *Slist;
slist = new(Slist);
slist.list.car = nil;
slist.list.cdr = nil;
slist.isatom = false;
slist.isstring = false;
retval = slist;
for ;; {
slist.list.car = Parse();
if token == ')' { // empty cdr
break;
}
if token == EOF { // empty cdr BUG SHOULD USE ||
break;
}
slist.list.cdr = new(Slist);
slist = slist.list.cdr;
}
return retval;
}
function atom(*Slist <- i int) { // BUG: uses tokenbuf; should take argument
var h, length int;
var slist, tail *Slist;
slist = new(Slist);
if token == '0' {
slist.atom.integer = i;
slist.isstring = false;
} else {
slist.atom.string = new([100]char);
var i int;
for i = 0; ; i = i + 1 {
(*slist.atom.string)[i] = tokenbuf[i];
if tokenbuf[i] == '.' {
break;
}
}
//slist.atom.string = "hello"; // BUG! s; //= strdup(s);
slist.isstring = true;
}
slist.isatom = true;
return slist;
}
function atoi(int <-) { // BUG: uses tokenbuf; should take argument
var v int = 0;
for i := 0; '0' <= tokenbuf[i] && tokenbuf[i] <= '9'; i = i + 1 {
v = 10 * v + convert(int, tokenbuf[i] - '0');
}
return v;
}
function Parse(*Slist <-) {
var slist *Slist;
if token == EOF || token == ')' {
return nil;
}
if token == '(' {
NextToken();
slist = ParseList();
Expect(')');
return slist;
} else {
// Atom
switch token {
case EOF:
return nil;
case '0':
slist = atom(atoi());
case '"':
case 'A':
slist = atom(0);
case:
slist = nil;
print "unknown token"; //, token, tokenbuf;
}
NextToken();
return slist;
}
return nil;
}
function OpenFile() {
//strcpy(input, TESTSTRING);
//inputindex = 0;
// (defn foo (add 12 34)).
inputindex = 0;
peekc = -1; // BUG
EOF = -1; // BUG
i := 0;
input[i] = '('; i = i + 1;
input[i] = 'd'; i = i + 1;
input[i] = 'e'; i = i + 1;
input[i] = 'f'; i = i + 1;
input[i] = 'n'; i = i + 1;
input[i] = ' '; i = i + 1;
input[i] = 'f'; i = i + 1;
input[i] = 'o'; i = i + 1;
input[i] = 'o'; i = i + 1;
input[i] = ' '; i = i + 1;
input[i] = '('; i = i + 1;
input[i] = 'a'; i = i + 1;
input[i] = 'd'; i = i + 1;
input[i] = 'd'; i = i + 1;
input[i] = ' '; i = i + 1;
input[i] = '1'; i = i + 1;
input[i] = '2'; i = i + 1;
input[i] = ' '; i = i + 1;
input[i] = '3'; i = i + 1;
input[i] = '4'; i = i + 1;
input[i] = ')'; i = i + 1;
input[i] = ')'; i = i + 1;
input[i] = '.'; i = i + 1;
NextToken();
}
该程序解析并打印一个 S-expression. 它不需要用户输入也没有导入,仅依赖于内置的 print
功能进行输出。从字面上看,它是在第一天写的,工作但很基础的编译器. 许多东西没有实现甚至没有指定语言.
尽管如此,该程序仍可以识别出语言如今的基本风格。类型和变量声明,控制流和包语句没有太大变化.
但是,有许多差异和缺失的地方。最重要的是缺少并发性和接口 - 从第一天起就被认为是必不可少的但还未设计的内容.
一个 func
是一个 function
, 其签名在 参数之前 指定了返回值,并用 <-
分隔它们,我们现在将其用作通道发送 / 接收运算符。例如,WhiteSpace
函数采用整数 c
并返回一个布尔值.
function WhiteSpace(bool <- c int)
该箭头是一种权宜之计,直到出现了用于声明多个返回值的更好的语法.
方法与函数不同,并且具有自己的关键字.
Methods were distinct from functions and had their own keyword.
method (this *Slist) Car(*Slist <-) {
return this.list.car;
}
并且方法已在结构定义中预先声明,尽管很快就发生了改变了.
type Slist struct {
...
Car method(*Slist <-);
}
尽管符合规范,但还是没有字符串。必须将输入字符串构建为具有笨拙构造的 uint8
数组. (数组是基本的,还没有设计切片,更不用说实现了,尽管有一个未实现的 "开放数组" 的概念.)
input[i] = '('; i = i + 1;
input[i] = 'd'; i = i + 1;
input[i] = 'e'; i = i + 1;
input[i] = 'f'; i = i + 1;
input[i] = 'n'; i = i + 1;
input[i] = ' '; i = i + 1;
...
panic
和 print
都是内置关键字,不是预先声明的函数.
print "parse error: expected ", c, ".";
panic "parse";
还有许多其他小的差异;看看如果你可以识别出来的话.
编写此程序不到两年,Go 就作为一个开源项目发布了。回顾过去,令人惊讶的是该语言已经发展和成熟了不少. (在此原型 Go 和我们今天所知道的 Go 之间进行的最后更改是消除分号.)
但更令人震惊的是,我们对 编写 Go 代码学到了多少。例如,Rob 将他的方法接收者称为 this
, 但是现在我们使用较短的上下文特定名称。有数百个更重要的示例,直到今天,我们仍在发现编写 Go 代码的更好方法. (查看 glog 软件包 的巧妙技巧,以处理 详细级别.)
我想期待明天我们又可以学会什么.
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
推荐文章: