Gox语言语法速览-GX4.2

Gox语言的语法基本脱胎于初始的Q语言(Qlang,现已改变路线,感谢原作者),下面是Q语言语法速览供参考。

Q Language Manual (Q语言手册)

运算符

基本上 Go 语言的操作符都支持,且操作符优先级相同。包括:

  • '+'、'-'、'*'、'/'、'%'、'='
  • ''、'&'、'&'、'|'、'<<'、'>>'
  • '+='、'-='、'*='、'/='、'%='、'++'、'--'
  • '='、'&='、'&='、'|='、'<<='、'>>='
  • '!'、'>='、'<='、'>'、'<'、'=='、'!='、'&&'、'||'
  • '<-' (chan操作符)

类型

原理上支持所有 Go 语言中的类型。典型有:

  • 基本类型:int、float (在 Go 语言里面是 float64)、string、byte、bool、var(在 Go 语言里面是 interface{})。
  • 复合类型:slice、map、chan
  • 用户自定义:函数(闭包)、类成员函数、类

常量

  • 布尔类型:true、false(由 builtin 模块支持)
  • var 类型:nil(由 builtin 模块支持)
  • 浮点类型:pi、e、phi (由 math 模块支持)

变量及初始化

基本类型

a = 1 // 创建一个 int 类型变量,并初始化为 1
b = "hello" // string 类型
c = true // bool 类型
d = 1.0 // float 类型
e = 'h' // byte 类型

string 类型

a = "hello, world"

和 Go 语言类似,string 有如下内置的操作:

a = "hello" + "world" // + 为字符串连接操作符
n = len(a) // 取 a 字符串的长度
b = a[1] // 取 a 字符串的某个字符,得到的 b 是 byte 类型
c = a[1:4] // 取子字符串

slice 类型

a = [1, 2, 3] // 创建一个 int slice,并初始化为 [1, 2, 3]
b = [1, 2.3, 5] // 创建一个 float slice
c = ["a", "b", "c"] // 创建一个 string slice
d = ["a", 1, 2.3] // 创建一个 var slice (等价于 Go 语言的 []interface{})
e = make([]int, len, cap) // 创建一个 int slice,并将长度设置为 len,容量设置为 cap
f = make([][]int, len, cap) // 创建一个 []int 的 slice,并将长度设置为 len,容量设置为 cap
g = []byte{1, 2, 3} // 创建一个 byte slice,并初始化为 [1, 2, 3]
h = []byte(nil) // 创建一个空 byte slice

和 Go 语言类似,slice 有如下内置的操作:

a = append(a, 4, 5, 6) // 含义与 Go 语言完全一致
n = len(a) // 取 a 的元素个数
m = cap(a) // 取 slice a 的容量
ncopy = copy(a, b) // 复制 b 的内容到 a,复制的长度 ncopy = min(len(a), len(b))
b1 = b[2] // 取 b 这个 slice 中 index=2 的元素
b[2] = 888 // 设置 b 这个 slice 中 index=2 的元素值为 888
b[1], b[2], b[3] = 777, 888, 999 // 设置 b 这个 slice 中 index=1, 2, 3 的三个元素值
b2 = b[1:4] // 取子slice

特别地,在 qlang 中可以这样赋值:

x, y, z = [1, 2, 3]

结果是 x = 1, y = 2, z = 3。

实际上,qlang 支持多返回值就是通过 slice 的多赋值完成:

  • 对于那些返回了多个值的 Go 函数,在 qlang 会理解为返回 var slice,也就是 []interface{}。

举个例子:

f, err = os.Open(fname)

这个例子,在 Go 里面返回的是 (*os.File, error)。但是 qlang 中是 var slice。

map 类型

a = {"a": 1, "b": 2, "c": 3} // 得到 map[string]int 类型的对象
b = {"a": 1, "b", 2.3, "c": 3} // 得到 map[string]float64 类型的对象
c = {1: "a", 2: "b", 3: "c"} // 得到 map[int]string 类型的对象
d = {"a": "hello", "b": 2.0, "c": true} // 得到 map[string]interface{} 类型的对象
e = make(map[string]int) // 创建一个空的 map[string]int 类型的对象
f = make(map[string]map[string]int) // 创建一个 map[string]map[string]int 类型的对象
g = map[string]int16{"a": 1, "b": 2} // 创建一个 map[string]int16 类型的对象

和 Go 语言类似,map 有如下内置的操作:

n = len(a) // 取 a 的元素个数
x = a["b"] // 取 a map 中 key 为 "b" 的元素
x = a.b // 含义同上,但如果 "b" 元素不存在会 panic
a["e"], a["f"], a["g"] = 4, 5, 6 // 同 Go 语言
a.e, a.f, a.g = 4, 5, 6 // 含义同 a["e"], a["f"], a["g"] = 4, 5, 6
delete(a, "e") // 删除 a map 中的 "e" 元素

需要注意的是,a["b"] 的行为和 Go 语言中略有不同。在 Go 语言中,常见的范式是:

x := map[string]int{"a": 1, "b": 2}
a, ok := x["a"] // 结果:a = 1, ok = true
if ok { // 判断a存在的逻辑
    ...
}
c, ok2 := x["c"] // 结果:c = 0, ok2 = false
d := x["d"] // 结果:d = 0

而在 qlang 中是这样的:

x = {"a": 1, "b": 2}
a = x["a"] // 结果:a = 1
if a != undefined { // 判断a存在的逻辑
    ...
}
c = x["c"] // 结果:c = undefined,注意不是0,也不是nil
d = x["d"] // 结果:d = undefined,注意不是0,也不是nil

chan 类型

ch1 = make(chan bool, 2) // 得到 buffer = 2 的 chan bool
ch2 = make(chan int) // 得到 buffer = 0 的 chan int
ch3 = make(chan map[string]int) // 得到 buffer = 0 的 chan map[string]int

和 Go 语言类似,chan 有如下内置的操作:

n = len(ch1) // 取得chan当前的元素个数
m = cap(ch1) // 取得chan的容量
ch1 <- true // 向chan发送一个值
v = <-ch1 // 从chan取出一个值
close(ch1) // 关闭chan,被关闭的chan是不能写,但是还可以读(直到已经写入的值全部被取完为止)

需要注意的是,在 chan 被关闭后,<-ch 取得 undefined 值。所以在 qlang 中应该这样:

v = <-ch1
if v != undefined { // 判断chan没有被关闭的逻辑
    ...
}

流程控制

if 语句

if booleanExpr1 {
    // ...
} elif booleanExpr2 {
    // ...
} elif booleanExpr3 {
    // ...
} else {
    // ...
}

switch 语句

switch expr {
case expr1:
    // ...
case expr2:
    // ...
default:
    // ...
}

或者:

switch {
case booleanExpr1:
    // ...
case booleanExpr2:
    // ...
default:
    // ...
}

for 语句

for { // 无限循环,需要在中间 break 或 return 结束
    ...
}

for booleanExpr { // 类似很多语言的 while 循环
    ...
}

for initExpr; conditionExpr; stepExpr {
    ...
}

典型例子:

for i = 0; i < 10; i++ {
    ...
}

另外我们也支持 for..range 语法:

for range collectionExpr { // 其中 collectionExpr 可以是 slice, map 或 chan
    ...
}

for index = range collectionExpr {
    ...
}

for index, value = range collectionExpr {
    ...
}

函数

函数和闭包

基本语法:

funcName = fn(arg1, arg2, argN) {
    //...
    return expr
}

这就定义了一个名为 funcName 的函数。

本质上来说,函数只是和 1、"hello" 类似的一个值,只是值的类型是函数类型。

所有的用户自定义函数,在 Go 里面实际类型均为 func(args ...interface{}) interface{}。

你可以在一个函数中引用外层函数的变量。如:

x = fn(a) {
    b = 1
    y = fn(t) {
        return b + t
    }
    return y(a)
}

println(x(3)) // 结果为 4

但是如果你直接修改外层变量会报错:

x = fn(a) {
    b = 1
    y = fn(t) {
        b = t // 这里会抛出异常,因为不能确定你是想定义一个新的 b 变量,还是要修改外层 x 函数的 b 变量
    }
    y(a)
    return b
}

如果你想修改外层变量,需要先引用它,如下:

x = fn(a) {
    b = 1
    y = fn(t) {
        b; b = t // 现在正常了,我们知道你要修改外层的 b 变量
    }
    y(a)
    return b
}

println(x(3)) // 输出 3

不定参数

和 Go 语言类似,qlang 也支持不定参数的函数。例如内置的 max、min 都是不定参数的:

a = max(1.2, 3, 5, 6) // a 的值为 float 类型的 6
b = max(1, 3, 5, 6) // b 的值为 int 类型的 6

也可以自定义一个不定参数的函数,如:

x = fn(fmt, args...) {
    printf(fmt, args...)
}

这样就得到了一个 x 函数,功能和内建的 printf 函数一模一样。

多赋值

形式上,qlang 和 Go 语言一样支持多赋值,也支持函数返回多个值:

x, y, z = 1, 2, 3.5
a, b, c = fn() {
    return 1, 2, 3.5 // 返回的是 var slice
}()

另外我们也支持:

x, y, z = [1, 2, 3.5]
a, b, c = fn() {
    return [1, 2, 3.5] // 返回的是 float slice
}()

需要注意的是,带上[]进行多赋值和不带[]进行多赋值在语义上有一点点不同。下面是例子:

x1, y1, z1 = 1, 2, 3.5
x2, y2, z2 = [1, 2, 3.5]
println(type(x1), type(x2))

结果表明 x1 的类型为 int,而 x2 的类型是 float。

defer

是的,qlang 也支持 defer。这在处理系统资源(如文件、锁等)释放场景非常有用。一个典型场景:

f, err = os.Open(fname)
if err != nil {
    // 做些出错处理
    return
}
defer f.Close()

// 正常操作这个 f 文件

值得注意的是:

在一个细节上 qlang 的 defer 和 Go 语言处理并不一致,那就是 defer 表达式中的变量值。在 Go 语言中,所有 defer 引用的变量均在 defer 语句时刻固定下来(如上面的 f 变量),后面任何修改均不影响 defer 语句的行为。但 qlang 是会受到影响的。例如,假设你在 defer 之后,调用 f = nil 把 f 变量改为 nil,那么后面执行 f.Close() 时就会 panic。

匿名函数

所谓匿名函数,是指:

fn {
    ... // 一段复杂代码
}

它等价于:

fn() {
    ... // 一段复杂代码
}()

以前在 defer 要执行一段很复杂的代码段时,我们往往这样写:

defer fn() {
    ... // 一段复杂代码
}()

有了匿名函数,我们可以简写为:

defer fn {
    ... // 一段复杂代码
}

一个用户自定义类型的基本语法如下:

Foo = class {
    fn SetAB(a, b) {
        this.a, this.b = a, b
    }
    fn GetA() {
        return this.a
    }
}

有了这个 class Foo,我们就可以创建 Foo 类型的 object 了:

foo = new Foo
foo.SetAB(3, "hello")
a = foo.GetA()
println(a) // 输出 3

构造函数

在 qlang 中,构造函数只是一个名为 _init 的成员方法(method):

Foo = class {
    fn _init(a, b) {
        this.a, this.b = a, b
    }
}

有了这个 class Foo 后,我们 new Foo 时就必须携带2个构造参数了:

foo = new Foo(3, "hello")
println(foo.a) // 输出 3

goroutine

和 Go 语言一样,qlang 中通过 go 关键字启动一个新的 goroutine。如:

go println("this is a goroutine")

一个比较复杂的例子:

wg = sync.NewWaitGroup()
wg.Add(2)

go fn {
    defer wg.Done()
    println("in goroutine1")
}

go fn {
    defer wg.Done()
    println("in goroutine2")
}

wg.Wait()

这是一个经典的 goroutine 使用场景,把一个 task 分为 2 个子 task,交给 2 个 goroutine 执行。

include

在 qlang 中,一个 .ql 文件可以通过 include 文法来将另一个 .ql 的内容包含进来。所谓包含,其实际的能力类似于将代码拷贝粘贴过来。例如,在某个目录下有 a.ql 和 b.ql 两个文件。

其中 a.ql 内容如下:

println("in script A")

foo = fn() {
    println("in func foo:", a, b)
}

其中 b.ql 内容如下:

a = 1
b = 2

include "a.ql"

println("in script B")
foo()

如果 include 语句的文件名不是以 .ql 为后缀,那么 qlang 会认为这是一个目录名,并为其补上 "/main.ql" 后缀。也就是说:

include "foo/bar.v1"

等价于:

include "foo/bar.v1/main.ql"

模块及 import

在 qlang 中,模块(module)是一个目录,该目录下要求有一个名为 main.ql 的文件。模块中的标识(ident)默认都是私有的。想要导出一个标识(ident),需要用 export 语法。例如:

a = 1
b = 2

println("in script A")

f = fn() {
    println("in func foo:", a, b)
}

export a, f

这个模块导出了两个标识(ident):整型变量 a 和 函数 f。

要引用这个模块,我们需要用 import 文法:

import "foo/bar.v1"
import "foo/bar.v1" as bar2

bar.a = 100 // 将 bar.a 值设置为 100
println(bar.a, bar2.a) // bar.a, bar2.a 的值现在都是 100

bar.f()

qlang 会在环境变量 QLANG_PATH 指示的目录列表中查找 foo/bar.v1/main.ql 文件。如个没有设置环境变量 QLANG_PATH,则会在 ~/qlang 目录中查找。

将一个模块 import 多次并不会出现什么问题,事实上第二次导入不会发生什么,只是增加了一个别名。

include vs. import

include 是拷贝粘贴,比较适合用于模块内的内容组织。比如一个模块比较复杂,全部写在 main.ql 文件中过于冗长,则可以用 include 语句分解到多个文件中。include 不会通过 QLANG_PATH 来找文件,它永远基于 __dir__(即 include 代码所在脚本的目录) 来定位文件。

import 是模块引用,适合用于作为业务分解的主要方式。import 基于 QLANG_PATH 这个环境变量搜寻被引用的模块,而不是基于 __dir__

与 Go 语言的互操作性

qlang 是一个嵌入式语言,它的定位是作为 Go 语言应用的运行时嵌入脚本。

作为 Go 语言的伴生语言,它与 Go 语言有极佳的互操作性。任何 Go 语言的函数,可以几乎不做任何包装就可以直接在 qlang 中使用。

这太爽了!

定制 qlang

除了 qlang 语言的 import 支持外,qlang 的 Go 语言开发包也支持 Go package 编写 qlang 模块。

qlang 采用微内核设计,大部分你看到的功能,都通过 Go package 形式编写的 qlang 模块提供。你可以按需定制 qlang。

你可以自由定制你想要的 qlang 的样子。在没有引入任何模块的情况下,qlang 连最基本的 '+'、'-'、'*'、'/' 都做不了,因为提供这个能力的是 builtin 包。

在前面“快速入门”给出的精简版本基础上,我们可以自由添加各种模块,如:

import (
    "qlang.io/lib/math"
    "qlang.io/lib/strconv"
    "qlang.io/lib/strings"
    ...
)

func main() {

    qlang.Import("math", math.Exports)
    qlang.Import("strconv", strconv.Exports)
    qlang.Import("strings", strings.Exports)

    ...
}

这样,在 qlang 中就可以用 math.sin, strconv.itoa 等函数了。

如果你嫌 math.sin 太长,还可以将 math 模块作为 builtin 功能导入。这只需要略微修改下导入的文法:

qlang.Import("", math.Exports) // 如此,你就可以直接用 sin 而不是 math.sin 了

制作 qlang 模块

制作 qlang 模块的成本极其低廉。我们打开 qlang.io/lib/strings 看看它是什么样的:

package strings

import (
    "strings"
)

var Exports = map[string]interface{}{
    "Contains":  strings.Contains,
    "Index":     strings.Index,
    "IndexAny":  strings.IndexAny,
    "Join":      strings.Join,
    "Title":     strings.Title,
    "ToLower":   strings.ToLower,
    "ToTitle":   strings.ToTitle,
    "ToUpper":   strings.ToUpper,
    "Trim":      strings.Trim,

    "NewReader":   strings.NewReader,
    "NewReplacer": strings.NewReplacer,
    ...
}

值得注意的一个细节是,我们几乎不需要对 Go 语言的 strings package 中的函数进行任何包装,你只需要把这个函数加入到导出表(Exports)即可。你也无需包装 Go 语言中的类,比如上面的我们导出了 strings.NewReplacer,但是我们不必去包装 strings.Replacer 类。这个类的所有功能可以直接使用。如:

strings.NewReplacer("?", "!").Replace("hello, world???") // 得到 "hello, world!!!"

这是 qlang 最强大的地方,近乎免包装。甚至,你可以写一个自动的 Go package 转 qlang 模块的工具,找到 Go package 所有导出的全局函数,加入到 Exports 表即完成了该 Go package 的包装,几乎零成本。

导出 Go 结构体

假设我们有 Go 结构体如下:

package foo

type Bar struct {
    X int
    Y string
}

func (p *Bar) GetX() int {
    return p.X
}

我们一样可以把它加入到 Export 表进行导出:

import (
    qlang "qlang.io/spec"
)

var Exports = map[string]interface{}{
    "Bar": qlang.StructOf((*foo.Bar)(nil)),
}

这样你就可以在 qlang 里面使用这个结构体:

bar = &foo.Bar{X: 1, Y: "hello"}
x = bar.GetX()
y = bar.Y
println(x, y)

反射

在任何时候,你都可以用 type 函数来查看一个变量的实际类型,结果在 Go 语言中是 reflect.Type。如:

t1 = type(1) // 相当于调用 Go 语言中的 reflect.TypeOf

用 type 可以很好地研究 qlang 的内在实现。比如:

t2 = type(fn() {})

我们得到了 *Function。这说明尽管用户自定义的函数原型多样,但是其 Go 类型是一致的。

我们也可以看看用户自定义的类型:

Foo = class { fn f() {} }
t1 = type(Foo)
t2 = type(Foo.f)

foo = new Foo
t3 = type(foo)
t4 = type(foo.f)

可以看到,class Foo 的 Go 类型是 *Class,而 object foo 的 Go 类型是 *Object。而 Foo.f 和普通用户自定义函数一致,也是 *Function,但 foo.f 不一样,它是 *Method 类型。

附录

样例代码

求最大素数

输入 n,求 < n 的最大素数。用法:

  • qlang maxprime.ql <N>
primes = [2, 3]
n = 1
limit = 9

isPrime = fn(v) {
    for i = 0; i < n; i++ {
        if v % primes[i] == 0 {
            return false
        }
    }
    return true
}

listPrimes = fn(max) {

    v = 5
    for {
        for v < limit {
            if isPrime(v) {
                primes = append(primes, v)
                if v * v >= max {
                    return
                }
            }
            v += 2
        }
        v += 2
        n; n++
        limit = primes[n] * primes[n]
    }
}

maxPrimeOf = fn(max) {

    if max % 2 == 0 {
        max--
    }

    listPrimes(max)
    n; n = len(primes)

    for {
        if isPrime(max) {
            return max
        }
        max -= 2
    }
}

// Usage: maxprime <Value>
//
if len(os.Args) < 2 {
    fprintln(os.Stderr, "Usage: maxprime <Value>")
    return
}

max, err = strconv.ParseInt(os.Args[1], 10, 64)
if err != nil {
    fprintln(os.Stderr, err)
    return 1
}
if max < 8 { // <8 的情况下,可直接建表,答案略
    return
}

max--
v = maxPrimeOf(max)
println(v)

计算器

实现一个支持四则运算及函数调用的计算器:


grammar = `

term = factor *('*' factor/mul | '/' factor/quo | '%' factor/mod)

doc = term *('+' term/add | '-' term/sub)

factor =
    FLOAT/pushFloat |
    '-' factor/neg |
    '(' doc ')' |
    (IDENT '(' doc %= ','/ARITY ')')/call
`

fntable = nil

Stack = class {

    fn _init() {
        this.stk = []
    }

    fn clear() {
        this.stk = this.stk[:0]
    }

    fn pop() {
        n = len(this.stk)
        if n > 0 {
            v = this.stk[n-1]
            this.stk = this.stk[:n-1]
            return v, true
        }
        return nil, false
    }

    fn push(v) {
        this.stk = append(this.stk, v)
    }

    fn popArgs(arity) {
        n = len(this.stk)
        if n < arity {
            panic("Stack.popArgs: unexpected")
        }
        args = make([]var, arity)
        copy(args, this.stk[n-arity:])
        this.stk = this.stk[:n-arity]
        return args
    }
}

Calculator = class {

    fn _init() {
        this.stk = new Stack
    }

    fn grammar() {
        return grammar
    }

    fn stack() {
        return this.stk
    }

    fn fntable() {
        return fntable
    }

    fn ret() {
        v, _ = this.stk.pop()
        this.stk.clear()
        return v
    }

    fn call(name) {
        f = fntable[name]
        if f == undefined {
            panic("function not found: " + name)
        }
        arity, _ = this.stk.pop()
        args = this.stk.popArgs(arity)
        ret = f(args...)
        this.stk.push(ret)
    }
}

fntable = {
    "sin": sin,
    "cos": cos,
    "pow": pow,
    "max": max,
    "min": min,

    "$mul": fn(a, b) { return a*b },
    "$quo": fn(a, b) { return a/b },
    "$mod": fn(a, b) { return a%b },
    "$add": fn(a, b) { return a+b },
    "$sub": fn(a, b) { return a-b },
    "$neg": fn(a) { return -a },

    "$call": Calculator.call,
    "$pushFloat": Stack.push,
    "$ARITY": Stack.push,
}

main { // 使用main关键字将主程序括起来,是为了避免其中用的局部变量比如 err 对其他函数造成影响

    calc = new Calculator
    engine, err = interpreter(calc, nil)
    if err != nil {
        fprintln(os.Stderr, err)
        return 1
    }

    scanner = bufio.NewScanner(os.Stdin)
    for scanner.Scan() {
        line = strings.Trim(scanner.Text(), " \t\r\n")
        if line != "" {
            err = engine.Eval(line)
            if err != nil {
                fprintln(os.Stderr, err)
            } else {
                printf("> %v\n\n", calc.ret())
            }
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,525评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,203评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,862评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,728评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,743评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,590评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,330评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,244评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,693评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,885评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,001评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,723评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,343评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,919评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,042评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,191评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,955评论 2 355