自学内容网 自学内容网

cangjie仓颉编程语言学习Note-2.标准库学习

cangjie仓颉编程语言学习Note-2.标准库学习

当前仓颉标准库提供了几乎涵盖常见开发所使用的常见库,这一点很赞!
详细模块如下:

  • std: 意指标准库,标准库是指在编程语言中预先定义的一组函数、类、结构体等,旨在提供常用的功能和工具,以便开发者能够更快速、更高效地编写程序。
    • core 包是标准库的核心包,提供了适用仓颉语言编程最基本的一些 API 能力。
    • argopt 包提供从命令行参数字符串解析出参数名和参数值的相关能力。
    • ast 包主要包含了仓颉源码的语法解析器和仓颉语法树节点,提供语法解析函数。
    • binary 包提供了基础数据类型和二进制字节数组的不同端序转换接口,以及端序反转接口。
    • collection 包提供了常见数据结构的高效实现、相关抽象的接口的定义以及在集合类型中常用的函数功能。
    • collection.concurrent 包提供了并发安全的集合类型实现。
    • console 包提供和标准输入、标准输出、标准错误进行交互的方法。
    • convert 包提供从字符串转到特定类型的 Convert 系列函数。
    • crypto.cipher 包提供对称加解密通用接口。
    • crypto.digest 包提供常用摘要算法的通用接口,包括 MD5、SHA1、SHA224、SHA256、SHA384、SHA512、HMAC、SM3。
    • database.sql 包提供仓颉访问数据库的接口。
    • deriving 包提供一组宏来自动生成接口实现。
    • ffi.python 包提供仓颉与 Python 语言互操作调用的能力,以兼容强大的计算和 AI 生态。
    • format 包提供格式化能力,主要为将仓颉类型实例转换为格式化字符串。
    • fs(file system)包提供对文件、文件夹、路径、文件元数据信息的一些操作函数。
    • io 包提供程序与外部设备进行数据交换的能力。
    • log 包提供日志管理和打印功能。(已废弃,请使用 log 包)
    • math 包提供常见的数学运算,常数定义,浮点数处理等功能。
    • math.numeric 包对基础类型可表达范围之外提供扩展能力。
    • net 包提供常见的网络通信功能。
    • objectpool 包提供了对象缓存和复用的功能。
    • posix 包主要适配 POSIX 系统接口。
    • process 包主要提供 Process 进程操作接口,主要包括进程创建,标准流获取,进程等待,进程信息查询等。
    • overflow 包提供了溢出处理相关能力。
    • random 包提供生成伪随机数的能力。
    • reflect 包提供了反射功能,使得程序在运行时能够获取到各种实例的类型信息,并进行各种读写和调用操作。
    • regex 包使用正则表达式分析处理文本的能力(仅支持 Ascii 编码字符串),支持查找、分割、替换、验证等功能。
    • runtime 包的作用是与程序的运行时环境进行交互,提供了一系列函数和变量,用于控制、管理和监视程序的执行。
    • socket 包用于进行网络通信,提供启动 Socket 服务器、连接 Socket 服务器、发送数据、接收数据等功能。
    • sort 包提供数组类型的排序函数。
    • sync 包提供并发编程相关的能力。
    • time 包提供了与时间相关的类型,包括日期时间,时间间隔,单调时间和时区等,并提供了计算和比较的功能。
    • unicode 包提供了按 unicode 编码标准处理字符的能力。
    • unittest 包用于编写仓颉项目单元测试代码,提供包括代码编写、运行和调测在内的基本功能。
  • compress: 模块提供压缩解压功能。
    • zlib 包提供压缩解压能力。
  • crypto: 模块提供安全加密能力。
    • crypto 包提供安全随机数功能。
    • digest 包提供常用的消息摘要算法,包括 MD5、SHA1、SHA224、SHA256、SHA384、SHA512、HMAC、SM3等。
    • keys 包提供非对称加密和签名算法,包括 RSA 和 SM2 非对称加密算法以及 ECDSA 签名算法。
    • x509 包提供处理数字证书功能,提供包括解析和序列化 X509 证书、验证证书、创建自签名证书、创建和验证证书链等主要功能。
  • encoding: 模块提供字符编解码功能。
    • base 包提供字符串的 Base64 编码及解码。
    • hex 包提供字符串的 Hex 编码及解码。
    • json 包用于对 json 数据的处理,实现 String, JsonValue, DataModel 之间的相互转换。
    • json.stream 包主要用于仓颉对象和 JSON 数据流之间的互相转换。
    • url 包提供了 URL 相关的能力,包括解析 URL 的各个组件,对 URL 进行编解码,合并 URL 或路径等。
  • fuzz: 模块提供基于覆盖率反馈的模糊测试能力。
    • fuzz 包为开发者提供基于覆盖率反馈的仓颉 fuzz 引擎及对应的接口,开发者可以编写代码对 API 进行测试。
  • log: 模块提供了日志记录相关的能力。
    • log 包提供了一个单一的日志API,它抽象了实际的日志实现。
  • logger: 模块提供文本格式和 JSON 格式日志打印功能。
    • logger 包提供文本格式和 JSON 格式日志打印功能。
  • net: 模块提供了网络通信相关的能力。
    • http 包提供 HTTP/1.1,HTTP/2,WebSocket 协议的 server、client 端实现。
    • tls 包用于进行安全加密的网络通信,提供创建 TLS 服务器、基于协议进行 TLS 握手、收发加密数据、恢复 TLS 会话等能力。
  • serialization: 模块提供了序列化和反序列化能力。
    • serialization 包提供了序列化和反序列化的能力。

下面我们以专题方式,串联所有模块。

常见数据结构

Array/List/Set/Map

常用的几种基础 Collection 类型,包含 Array、ArrayList、HashSet、HashMap。他们都是引用类型

  • Array:如果我们不需要增加和删除元素,但需要修改元素,就应该使用它。
  • ArrayList:如果我们需要频繁对元素增删查改,就应该使用它。
  • HashSet:如果我们希望每个元素都是唯一的,就应该使用它。
  • HashMap:如果我们希望存储一系列的映射关系,就应该使用它。
import std.collection.*

main() {
    let list = ArrayList<Int64>([0, 1, 2])
    for (i in list) {
        println("The element is ${i}")
    }
    let a1 = list[0] // a == 0
    let a2 = list[1] // b == 1
    let a3 = list[-1] // Runtime exceptions
    list[0] = 3  // ok

    let b = ArrayList<String>(100) // size = 100
    let c = ArrayList<String>(2, {x: Int64 => x.toString()})  // size = 2,  init with x
}

// HashSet
let mySet = HashSet<Int64>([0, 1, 2])
let a = mySet.contains(0) // a == true
let b = mySet.contains(-1) // b == false
mySet.put(0)
mySet.remove(1) 

// HashMap
import std.collection.*

main() {
    let map = HashMap<String, Int64>([("a", 0), ("b", 1), ("c", 2)])
    for ((k, v) in map) {
        println("The key is ${k}, the value is ${v}")
    }
    let b = map["b"] // b == 1
    let c = map["d"] // Runtime exceptions
    map.contains("a") // true
    map["a"] = 3 // ok
    map.put("a", 0) // ok
    map.remove("d")
}

Range、Array、ArrayList 其实都是通过 Iterable 来支持 for-in 语法的。

interface Iterable<T> {
    func iterator(): Iterator<T>
    ...
}

interface Iterator<T> <: Iterable<T> {
    mut func next(): Option<T>
    ...
}

// 所以
let list = [1, 2, 3]
for (i in list) {
    println(i)
}

// 它等价如下代码
var it = list.iterator()
while (true) {
    match (it.next()) {
        case Some(i) => println(i)
        case None => break
    }
}
// or
while (let Some(i) <- it.next()) {
    println(i)
}

Exception/Error

在仓颉中,异常类有 ErrorException

  • Error 类描述仓颉语言运行时,系统内部错误和资源耗尽错误,应用程序不应该抛出这种类型错误,如果出现内部错误,只能通知给用户,尽量安全终止程序。
  • Exception 类描述的是程序运行时的逻辑错误或者 IO 错误导致的异常,例如数组越界或者试图打开一个不存在的文件等,这类异常需要在程序中捕获处理。

用户不可以通过继承仓颉语言内置的 Error 或其子类类来自定义异常,但是可以继承内置的 Exception 或其子类来自定义异常

open class FatherException <: Exception {
    public init() {
        super("This is FatherException.")
    }
    public open override func getClassName(): String {
        "FatherException"
    }
}

class ChildException <: FatherException {
    public init() {
        super("This is ChildException.")
    }
    public open override func getClassName(): String {
        "ChildException"
    }
}

下面列表展示了 Exception 的主要函数及其说明:

函数种类函数及说明
构造函数init() 默认构造函数。
构造函数init(message: String) 可以设置异常消息的构造函数。
成员属性open prop message: String 返回发生异常的详细信息。该消息在异常类构造函数中初始化,默认空字符串。
成员函数open func toString(): String 返回异常类型名以及异常的详细信息,其中,异常的详细信息会默认调用 message。
成员函数func getClassName(): String 返回用户定义的类名,子类需要重写该方法以返回子类的名称。
成员函数func printStackTrace(): Unit 打印堆栈信息至标准错误流。

下面列表展示了 Error 的主要函数及其说明:

函数种类函数及说明
成员属性open prop message: String 返回发生错误的详细信息。该消息在错误发生时,内部初始化,默认空字符串。
成员函数open func toString(): String 返回错误类型名以及错误的详细信息,其中,错误的详细信息会默认调用 message。
成员函数func getClassName(): String 返回用户定义的类名,子类需要重写该方法以返回子类的名称。
成员函数func printStackTrace(): Unit 打印堆栈信息至标准错误流。

异常的捕获

// 普通方式
main() {
    try {
        throw NegativeArraySizeException("I am an Exception!")
    } catch (e: OverflowException) {
        println(e.message)
        println("OverflowException is caught!")
    } catch (e: IllegalArgumentException | NegativeArraySizeException) {
        println(e.message)
        println("IllegalArgumentException or NegativeArraySizeException is caught!")
    } catch ( _ ) {   // 它可以捕获所有异常
        println("catch an exception!")
    } finally {
        println("finally is executed!")
    }
    println("This will also be printed!")
}

// try-with-resource
// 对象必须实现 Resource 接口才可以使用
class R <: Resource {
    public func isClosed(): Bool {
        true
    }
    public func close(): Unit {
        print("R is closed")
    }
}

main() {
    try (r = R()) {
        println("Get the resource")
        let r = 0 // Error, redefinition
        println(r)
    }
}

Option

Option 是一种非常常用的类型,所以仓颉为其提供了多种解构方式,具体包括:模式匹配、getOrThrow 函数、coalescing 操作符(??),以及问号操作符(?)。

  1. 模式匹配 match
func getString(p: ?Int64): String{
    match (p) {
        case Some(x) => "${x}"
        case None => "none"
    }
}
main() {
    let a = Some(1)
    let b: ?Int64 = None  // ? 等效于 Option<T>
    let r1 = getString(a)
    let r2 = getString(b)
    println(r1) // 1
    println(r2) // none
}
  1. coalescing 即 ??
main() {
    let a = Some(1)
    let b: ?Int64 = None
    let r1: Int64 = a ?? 0  // 如果 a == None, 则返回 0. 否则返回 a 的 Some(v)中的 v .
    let r2: Int64 = b ?? 0  // 一样的
    println(r1) // 1
    println(r2) // 0
}
  1. ?. 操作符,和?类型不一样. 用以实现 Option 类型对 .()[]{} 的支持
// 结构体支持
struct R {
    public var a: Int64
    public init(a: Int64) {
        this.a = a
    }
}

let r = R(100)
let x = Some(r)
let y = Option<R>.None
let r1 = x?.a   // r1 = Option<Int64>.Some(100)
let r2 = y?.a   // r2 = Option<Int64>.None

// 对class的支持
struct A {
    let b: B = B()
}

struct B {
    let c: Option<C> = C()
    let c1: Option<C> = Option<C>.None
}

struct C {
    let d: Int64 = 100
}

let a = Some(A())
let a1 = a?.b.c?.d // a1 = Option<Int64>.Some(100)
let a2 = a?.b.c1?.d // a2 = Option<Int64>.None
  1. getOrThrow 支持
main() {
    let a = Some(1)
    let b: ?Int64 = None
    let r1 = a.getOrThrow()  // Option 转 Exception
    println(r1)
    try {
        let r2 = b.getOrThrow()
    } catch (e: NoneValueException) {
        println("b is None")
    }
}

IO 操作

仓颉编程语言将标准输入输出、文件操作、网络数据流、字符串流、加密流、压缩流等等形式的操作,统一用 Stream 描述。Stream 主要面向处理原始二进制数据,Stream 中最小的数据单元是 Byte。仓颉编程语言将 Stream 定义成了 interface,它让不同的 Stream 可以用装饰器模式进行组合,极大地提升了可扩展性。

仓颉编程语言中常见的节点流包含标准流(StdIn、StdOut、StdErr)、文件流(File)、网络流(Socket)等。

标准流包含了标准输入流(stdin)、标准输出流(stdout)和标准错误输出流(stderr)。使用 Console 类型来分别访问它们.

import std.console.*

main() {
    // in
    let txt = Console.stdIn.readln()
    println(txt ?? "")
    
    // out
    for (i in 0..1000) {
        Console.stdOut.writeln("hello, world!")
    }
    Console.stdOut.flush()
}

// 文件操作
import std.fs.*

main() {
    let exist = exists("./tempFile.txt")
    println("exist: ${exist}")
    
    copy("./tempFile.txt", to: "./tempFile2.txt", overwrite: false)
    rename("./tempFile2.txt",  to: "./tempFile3.txt", overwrite: false)
    remove("./tempFile3.txt")
    
    let bytes = File.readFrom("./tempFile.txt") // 一次性读取了所有的数据
    File.writeTo("./otherFile.txt", bytes) // 把数据一次性写入另一个文件中
}

// 文件也是流
// public class File <: Resource & IOStream & Seekable
internal import std.fs.*
internal import std.io.*

main() {
    let file = File.create("./tempFile.txt")
    file.write("hello, world!".toArray())

    // 打开
    let file2 = File.openRead("./tempFile.txt")
    let bytes = readToEnd(file2) // 读取所有数据
    println(bytes)
    
    // try
    try (file3 = File.openRead("./tempFile.txt")) {
        ...
        // 结束使用后自动释放文件
    }
}

仓颉编程语言中常见的处理流包含 BufferedInputStreamBufferedOutputStreamStringReaderStringWriterChainedInputStream 等。

import std.io.*

main(): Unit {
    let arr1 = "012\n346789".toArray()
    let byteBuffer = ByteBuffer()
    byteBuffer.write(arr1)
    let stringReader = StringReader(byteBuffer)

    /* 读取一行数据 */
    let line = stringReader.readln()
    println(line ?? "error") // 012
}

main(): Unit {
    let byteBuffer = ByteBuffer()
    let stringWriter = StringWriter(byteBuffer)

    /* 写入字符串 */
    stringWriter.write("number")

    /* 写入字符串并自动转行 */
    stringWriter.writeln(" is:")

    /* 写入数字 */
    stringWriter.write(100.0f32)

    stringWriter.flush()

    println(String.fromUtf8(readToEnd(byteBuffer))) // number is:\n100.000000
}

Socket

在仓颉标准库中,用户可使用 std 模块下的 socket 包来实现传输层网络通信。
在传输层协议中,分为不可靠传输和可靠传输两种,仓颉将其抽象为 DatagramSocket 和 StreamSocket。其中不可靠传输协议常见的是 UDP,可靠传输协议常见的是 TCP,仓颉分别将其抽象为 UdpSocket 和 TcpSocket。另外,仓颉也实现了对传输层 Unix Domain 协议的支持,并支持其通过可靠和不可靠传输两种方式进行通信。
在应用层协议中,较为常见的是 HTTP 协议, 仓颉目前支持 HTTP/1.1、HTTP/2.0 等, 并支持从 HTTP 协议升级至 WebSocket 协议。

需要注意的是,仓颉的网络编程是阻塞式的。但被阻塞的是仓颉线程,阻塞中的仓颉线程会将系统线程让渡出去,因此并不会真正阻塞一个系统线程。

Tcp Server and client

package cjdemo

import std.socket.*
import std.time.*
import std.sync.*
import std.net.*

var SERVER_PORT: UInt16 = 0

func runTcpServer() {
    try (serverSocket = TcpServerSocket(bindAt: SERVER_PORT)) {
        serverSocket.bind()
        SERVER_PORT = (serverSocket.localAddress as IPSocketAddress)?.port ?? 0
        println("server port: ${SERVER_PORT}")
        try (client = serverSocket.accept()) {
            println("client: ${client.localAddress} ${client.remoteAddress}")
            
            let buf = Array<Byte>(10, repeat: 0)
            let count = client.read(buf)

            // 服务端读取到的数据为: [1, 2, 3, 0, 0, 0, 0, 0, 0, 0]
            println("Server read ${count} bytes: ${buf}") // count = 3
        }
    }
}

main(): Int64 {
    // 线程中执行tcp server
    let future = spawn {
        runTcpServer()
    }
    sleep(Duration.millisecond * 500)
    
    // tcp client
    try (socket = TcpSocket("127.0.0.1", SERVER_PORT)) {
        socket.connect()
        socket.write([1, 2, 3])
    }

    future.get()

    return 0
}

Http Server + websocket

注意:cangjie的 http/websocket 依赖 OpenSSL的lib和dll.
需要参考 https://docs.cangjie-lang.cn/docs/0.53.13/libs/net/http/http_package_overview.html 手动将openssl环境配好。

package cjdemo

import net.http.*
import encoding.url.*
import std.time.*
import std.sync.*
import std.collection.*
import log.*

// 1. 构建 Http Server 实例
let server = ServerBuilder()
    .addr("127.0.0.1")
    .port(0)
    .build()

// http server websocket handler
func ws_handler(ctx: HttpContext): Unit {
    // 完成 websocket 握手,获取 websocket 实例
    let websocketServer = WebSocket.upgradeFromServer(ctx)

    // 读取发送过来的frame到data
    let data = ArrayList<UInt8>()
    var frame = websocketServer.read()
    while(true) {
        match(frame.frameType) {
            case ContinuationWebFrame =>
                data.appendAll(frame.payload)
                if (frame.fin) {
                    break
                }
            case TextWebFrame | BinaryWebFrame =>
                if (!data.isEmpty()) {
                    throw Exception("invalid frame")
                }
                data.appendAll(frame.payload)
                if (frame.fin) {
                    break
                }
            case CloseWebFrame =>
                websocketServer.write(CloseWebFrame, frame.payload)
                break
            case PingWebFrame =>
                websocketServer.writePongFrame(frame.payload)
            case _ => ()
        }
        frame = websocketServer.read()
    }
    println("server data: ${String.fromUtf8(data.toArray())}")    // hello

    // 发送数据
    // 发 256 个 a
    websocketServer.write(TextWebFrame, Array<UInt8>(256, { _ => 97 }))
    
    // 收发 CloseFrame (由client 发起)
    let websocketFrame = websocketServer.read()
    println("server close frame type: ${websocketFrame.frameType}")   // CloseWebFrame
    println("server close frame payload: ${websocketFrame.payload}")     // 3, 232
    websocketServer.write(CloseWebFrame, websocketFrame.payload)
    // 关闭底层连接
    websocketServer.closeConn()
}

func startServer(): Unit {
    // 2. 注册请求处理逻辑
    server.distributor.register("/hello", {httpContext =>
        httpContext.responseBuilder.body("Hello Cangjie!")
    })

    server.distributor.register("/ws", ws_handler)

    server.logger.level = LogLevel.OFF
    // 3. 启动服务
    server.serve()
}

// 客户端复用 http client进行websocket连接
func client_ws(c:Client) {
    let url = URL.parse("ws://127.0.0.1:${server.port}/ws")
    let (ws, headers) = WebSocket.upgradeFromClient(c, url)

    for( (k,v) in  headers) {
        println("ws header => ${k} : ${v.toArray()}")
    }
    println()
    
    // 发送 hello
    ws.write(TextWebFrame, "hello from client".toArray())

    // 接受消息
    let data = ArrayList<UInt8>()
    var frame = ws.read()
    while(true) {
        match(frame.frameType) {
            case ContinuationWebFrame =>
                data.appendAll(frame.payload)
                if (frame.fin) {
                    break
                }
            case TextWebFrame | BinaryWebFrame =>
                if (!data.isEmpty()) {
                    throw Exception("invalid frame")
                }
                data.appendAll(frame.payload)
                if (frame.fin) {
                    break
                }
            case CloseWebFrame =>
                ws.write(CloseWebFrame, frame.payload)
                break
            case PingWebFrame =>
                ws.writePongFrame(frame.payload)
            case _ => ()
        }
        frame = ws.read()
    }
    println("client recv data size: ${data.size}")      // 4097
    println("client recv: ${String.fromUtf8(data.toArray())}")        // a

    // 收发 CloseFrame
    ws.writeCloseFrame(status: 1000)
    let websocketFrame = ws.read()
    println("client close frame type: ${websocketFrame.frameType}")      // CloseWebFrame
    println("client close frame payload: ${websocketFrame.payload}")     // 3, 232
    // 关闭底层连接
    ws.closeConn()

    //server.close()
}

func startClient(): Unit {
    // 1. 构建 client 实例
    let client = ClientBuilder().build()
    // 2. 发送 request
    let response = client.get("http://127.0.0.1:${server.port}/hello")
    // 3. 读取response body
    let buffer = Array<Byte>(1024, repeat: 0);
    for( (k,v) in response.headers ) {
        println("header => ${k} : ${v.toArray()}")
    }

    println()
    let length = response.body.read(buffer)
    println(String.fromUtf8(buffer[..length]))

    // 请求 websocket
    println("\r\nconnect to ws....")
    client_ws(client)

    // 4. 关闭连接
    client.close()
}

main () {
    spawn {
        startServer()
    }
    sleep(Duration.second)
    println("http server liston on: ${server.addr} ${server.port}")

    startClient()
}

输出

http server liston on: 127.0.0.1 53610
header => connection : [keep-alive]
header => date : [Fri, 01 Nov 2024 06:29:34 GMT]
header => content-length : [14]

Hello Cangjie!

connect to ws....
ws header => upgrade : [websocket]
ws header => connection : [upgrade]
ws header => sec-websocket-accept : [wsfGZnzlkFVlbBYKIcUZyqVvZqs=]
ws header => date : [Fri, 01 Nov 2024 06:29:34 GMT]
ws header => content-length : [0]

server data: hello from client
client recv data size: 256
client recv: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
server close frame type: CloseWebFrame
server close frame payload: [3, 232]
client close frame type: CloseWebFrame
client close frame payload: [3, 232]

并发

仓颉编程语言提供抢占式的线程模型作为并发编程机制, 可以细化为两种不同概念,语言线程和 native 线程。

允许多个语言线程在多个 native 线程上切换执行,这种也被称为 M:N 线程模型,即 M 个语言线程在 N 个 native 线程上调度执行,其中 M 和 N 不一定相等。
当前,仓颉语言的实现同样采用 M:N 线程模型;因此,仓颉线程本质上是一种用户态的轻量级线程,支持抢占且相比操作系统线程更轻量化。

import std.sync.*
import std.time.*

main(): Int64 {
    spawn { =>
        println("New thread before sleeping")
        sleep(100 * Duration.millisecond) // sleep for 100ms.
        println("New thread after sleeping")
        println("Current thread id: ${Thread.currentThread.id}")
    }
    // fut.get() // wait for the thread to finish.
    
    println("Main thread")
    return 0
}

在上面的例子中,新创建的线程会由于主线程结束而提前结束,在缺乏顺序保证的情况下,甚至可能会出现新创建的线程还来不及得到执行就退出了。我们可以通过 spawn 表达式的返回值,来等待线程执行结束。

spawn 表达式的返回类型是 Future<T>,其中 T 是类型变元,其类型与 lambda 表达式的返回类型一致。当我们调用 Future<T>get() 成员函数时,它将等待它的线程执行完成。

终止一个线程

可以通过 Future<T>cancel() 方法向对应的线程发送终止请求,该方法不会停止线程执行。开发者需要使用 ThreadhasPendingCancellation 属性来检查线程是否存在终止请求。

import std.sync.SyncCounter

main(): Unit {
    let syncCounter = SyncCounter(1)
    let fut = spawn {
        syncCounter.waitUntilZero()
        // Check cancellation request
        if (Thread.currentThread.hasPendingCancellation) {
            println("cancelled")
            return
        }
        println("hello")
    }
    fut.cancel()    // Send cancellation request
    syncCounter.dec()
    fut.get() // Join thread
}

线程同步

仓颉编程语言提供三种常见的同步机制来确保数据的线程安全:原子操作、互斥锁和条件变量。

  • 原子操作 Atomic, 提供整数类型、Bool 类型和引用类型的原子操作
    • loade/store/swap/compareAndSwap/fetchAdd/fetchSub/fetchAnd/fetchOr/fetchXor
    • 类型 AtomicInt8/16/32/64 ...
import std.sync.*
import std.time.*
import std.collection.*

let count = AtomicInt64(0)

main(): Int64 {
    let list = ArrayList<Future<Int64>>()

    // create 1000 threads.
    for (i in 0..1000) {
        let fut = spawn {
            sleep(Duration.millisecond) // sleep for 1ms.
            count.fetchAdd(1)
        }
        list.append(fut)
    }

    // Wait for all threads finished.
    for (f in list) {
        f.get()
    }

    let val = count.load()
    println("count = ${val}")
    return 0
}
  • 可重入互斥锁 ReentrantMutex, 对临界区加以保护,使得任意时刻最多只有一个线程能够执行临界区的代码
    1. 在访问共享数据之前,必须尝试获取锁;
    2. 处理完共享数据后,必须进行解锁,以便其他线程可以获得锁。
import std.sync.*
import std.time.*
import std.collection.*

var count: Int64 = 0
let mtx = ReentrantMutex()

main(): Int64 {
    let list = ArrayList<Future<Unit>>()

    // creat 1000 threads.
    for (i in 0..1000) {
        let fut = spawn {
            sleep(Duration.millisecond) // sleep for 1ms.
            mtx.lock()
            count++
            mtx.unlock()
        }
        list.append(fut)
    }

    // Wait for all threads finished.
    for (f in list) {
        f.get()
    }

    println("count = ${count}")
    return 0
}
  • Monitor 是一个内置的数据结构,它绑定了互斥锁和单个与之相关的条件变量(也就是等待队列)。
import std.sync.*
import std.time.*

var mon = Monitor()
var flag: Bool = true

main(): Int64 {
    let fut = spawn {
        mon.lock()
        while (flag) {
            println("New thread: before wait")
            mon.wait()
            println("New thread: after wait")
        }
        mon.unlock()
    }

    // Sleep for 10ms, to make sure the new thread can be executed.
    sleep(10 * Duration.millisecond)

    mon.lock()
    println("Main thread: set flag")
    flag = false
    mon.unlock()

    mon.lock()
    println("Main thread: notify")
    mon.notifyAll()
    mon.unlock()

    // wait for the new thread finished.
    fut.get()
    return 0
}
  • MultiConditionMonitor 是一个内置的数据结构,它绑定了互斥锁和一组与之相关的动态创建的条件变量。该类应仅当在 Monitor 类不足以满足复杂的线程间同步的场景下使用。

  • 仓颉编程语言提供一个 synchronized 关键字,搭配 ReentrantMutex 一起使用,可以在其后跟随的作用域内自动进行加锁解锁操作,用来解决类似的问题。

import std.sync.*
import std.time.*
import std.collection.*

var count: Int64 = 0
let mtx = ReentrantMutex()

main(): Int64 {
    let list = ArrayList<Future<Unit>>()

    // creat 1000 threads.
    for (i in 0..1000) {
        let fut = spawn {
            sleep(Duration.millisecond) // sleep for 1ms.
            // Use synchronized(mtx), instead of mtx.lock() and mtx.unlock().
            synchronized(mtx) {
                count++
            }
        }
        list.append(fut)
    }

    // Wait for all threads finished.
    for (f in list) {
        f.get()
    }

    println("count = ${count}")
    return 0
}
  • 线程局部变量 ThreadLocal

main(): Int64 {
    let tl = ThreadLocal<Int64>()
    let fut1 = spawn {
        tl.set(123)
        println("tl in spawn1 = ${tl.get().getOrThrow()}")  // 123
    }
    let fut2 = spawn {
        tl.set(456)
        println("tl in spawn2 = ${tl.get().getOrThrow()}")  // 456
    }
    fut1.get()
    fut2.get()
    0
}

package

在仓颉编程语言中,包是编译的最小单元,每个包可以单独输出 AST 文件、静态库文件、动态库文件等产物。每个包有自己的名字空间,在同一个包内不允许有同名的顶层定义或声明(函数重载除外)。

模块是若干包的集合,是第三方开发者发布的最小单元。一个模块的程序入口只能在其根目录下,它的顶层最多只能有一个作为程序入口的 main ,该 main 没有参数或参数类型为 Array<String>,返回类型为整数类型或 Unit 类型。

声明

// file 1
// Comments are accepted
package test
// declarations...

// file 2
let a = 1 // Error, package declaration must appear first in a file
package test
// declarations...

导入

package a
import std.math.*
import package1.foo
import {package1.foo, package2.bar}

// 重命名
import p1 as A
import p1 as B

诸如 StringRange 等类型能直接使用,并不是因为这些类型是内置类型,而是因为编译器会自动为源码隐式的导入 core 包中所有的 public 修饰的声明。

宏可以理解为一种特殊的函数。一般的函数在输入的值上进行计算,然后输出一个新的值,而宏的输入和输出都是程序本身。

let x = 3
let y = 2
@dprint(x)        // 打印 "x = 3"
@dprint(x + y)    // 打印 "x + y = 5"


// dprint 的实现
macro package define

import std.ast.*

public macro dprint(input: Tokens): Tokens {
    let inputStr = input.toString()
    let result = quote(
        print($(inputStr) + " = ")
        println($(input)))
    return result
}

仓颉提供了几个内置编译标记,用于在编译时获取源代码的位置。

  • @sourcePackage() 展开后是一个 String 类型的字面量,内容为当前宏所在的源码的包名
  • @sourceFile() 展开后是一个 String 类型的字面量,内容为当前宏所在的源码的文件名
  • @sourceLine() 展开后是一个 Int64 类型的字面量,内容为当前宏所在的源码的代码行

内置编译标记 @When 来完成条件编译,编译条件使用 [] 括起来,[] 内支持输入一组或多组编译条件。@When 可以作用于导入节点和除 package 外的声明节点。

@When[os == "Linux"]  // Linux 系统中可以正确编译执行
class mc{}

main(): Int64 {
    var a = mc()
    return 0
}


@When[os == "Linux"]
func foo() {
    print("Linux, ")
}
@When[os == "Windows"]
func foo() {
    print("Windows, ")
}
@When[os != "Windows"]
func fee() {
    println("NOT Windows")
}
@When[os != "Linux"]
func fee() {
    println("NOT Linux")
}
main() {
    foo()
    fee()
}

FastNative

为了提升与 C 语言互操作的性能,仓颉提供 @FastNative 标记用于优化对 C 函数的调用。值得注意的是 @FastNative 只能用于 foreign 声明的函数。

@FastNative
foreign func strlen(str: CPointer<UInt8>): UIntNative

反射

仓颉的动态特性主要包含反射、动态加载。

反射需要TypeInfo这个核心类型:

public class TypeInfo {
    public static func of(a: Any): TypeInfo
    public static func of(a: Object): ClassTypeInfo
    public static func of<T>(): TypeInfo
}

let t1: TypeInfo = TypeInfo.get("Int64")  // 必须已经被实例化了


import std.reflect.*

public class Foo {
    public static var param1 = 20
    public var param2 = 10
}

main(): Unit{
    let obj = Foo()
    let info = TypeInfo.of(obj)
    let staticVarInfo = info.getStaticVariable("param1")
    let instanceVarInfo = info.getInstanceVariable("param2")
    println("成员变量初始值")
    print("Foo 的静态成员变量 ${staticVarInfo} = ")
    println((staticVarInfo.getValue() as Int64).getOrThrow())
    print("obj 的实例成员变量 ${instanceVarInfo} = ")
    println((instanceVarInfo.getValue(obj) as Int64).getOrThrow())
    println("更改成员变量")
    staticVarInfo.setValue(8)
    instanceVarInfo.setValue(obj, 25)
    print("Foo 的静态成员变量 ${staticVarInfo} = ")
    println((staticVarInfo.getValue() as Int64).getOrThrow())
    print("obj 的实例成员变量 ${instanceVarInfo} = ")
    println((instanceVarInfo.getValue(obj) as Int64).getOrThrow())
    return
}

动态加载

动态加载指的是仓颉程序可以在运行过程中通过特定函数来访问仓颉动态模块,以此读写全局变量、调用全局函数、获取类型信息的能力。主要通过 ModuleInfoPackageInfo 这两个类型来提供动态加载的能力。

例如我们存在一个 module0 模块下的 package0 包含一个公开的类型 Foo,其对应的仓颉动态模块路径为 “./module_package.so”

let m = ModuleInfo.load("./module_package")
let p = m.getPackageInfo("package0").getOrThrow()
let at = TypeInfo.get("module0/package0.Foo")

注释

一些属性宏用来支持一些特殊情况的处理。
仓颉中提供三种属性宏来控制整数溢出的处理策略,即 @OverflowThrowing@OverflowWrapping@OverflowSaturating

发者可以通过自定义类型标注 @Annotation 方式创建自己的自定义注解。@Annotation 只能修饰 class,并且不能是 abstractopensealed 修饰的 class。当一个 class 声明它标注了 @Annotation,那么它必须要提供至少一个 const init 函数,否则编译器会报错。

仓颉与C交互

仓颉调用 C 的函数

在仓颉中要调用 C 的函数,需要在仓颉语言中用 @Cforeign 关键字声明这个函数,但 @C 在修饰 foreign 声明的时候,可以省略。
需要注意的是:

  1. foreign 修饰函数声明,代表该函数为外部函数。被 foreign 修饰的函数只能有函数声明,不能有函数实现。
  2. foreign 声明的函数,参数和返回类型必须符合 C 和仓颉数据类型之间的映射关系。
  3. 由于 C 侧函数很可能产生不安全操作,所以调用 foreign 修饰的函数需要被 unsafe 块包裹,否则会发生编译错误。
  4. @C 修饰的 foreign 关键字只能用来修饰函数声明,不可用来修饰其他声明,否则会发生编译错误。
  5. @C 只支持修饰 foreign 函数、top-level 作用域中的非泛型函数和 struct 类型。
  6. foreign 函数不支持命名参数和参数默认值。foreign 函数允许变长参数,使用 ... 表达,只能用于参数列表的最后。变长参数均需要满足 CType 约束,但不必是同一类型。
  7. 仓颉(CJNative 后端)虽然提供了栈扩容能力,但是由于 C 侧函数实际使用栈大小仓颉无法感知,所以 ffi 调用进入 C 函数后,仍然存在栈溢出的风险,需要开发者根据实际情况,修改 cjStackSize 的配置。

假设有2个c的函数

// stdlib.h
int rand();

// stdio.h
int printf (const char *fmt, ...);

需要在cangjie中声明,然后才能使用:

// declare the function by `foreign` keyword, and omit `@C`
foreign func rand(): Int32
foreign func printf(fmt: CString, ...): Int32

main() {
    // call this function by `unsafe` block
    let r = unsafe { rand() }
    println("random number ${r}")
    unsafe {
        var fmt = LibC.mallocCString("Hello, No.%d\n")
        printf(fmt, 1)
        LibC.free(fmt)
    }
}

综合的demo

foreign func malloc(size: UIntNative): CPointer<Unit>
foreign func free(ptr: CPointer<Unit>): Unit

@C
struct Point3D {
    var x: Int64
    var y: Int64
    var z: Int64

    init(x: Int64, y: Int64, z: Int64) {
        this.x = x
        this.y = y
        this.z = z
    }
}

main() {
    let p1 = CPointer<Point3D>() // create a CPointer with null value
    if (p1.isNull()) {  // check if the pointer is null
        print("p1 is a null pointer")
    }

    let sizeofPoint3D: UIntNative = 24
    var p2 = unsafe { malloc(sizeofPoint3D) }    // malloc a Point3D in heap
    var p3 = unsafe { CPointer<Point3D>(p2) }    // pointer type cast

    unsafe { p3.write(Point3D(1, 2, 3)) } // write data through pointer

    let p4: Point3D = unsafe { p3.read() } // read data through pointer

    let p5: CPointer<Point3D> = unsafe { p3 + 1 } // offset of pointer

    unsafe { free(p2) }
}

类型匹配

Cangjie TypeC TypeSize (byte)
Unitvoid0
Boolbool1
UInt8char1
Int8int8_t1
UInt8uint8_t1
Int16int16_t2
UInt16uint16_t2
Int32int32_t4
UInt32uint32_t4
Int64int64_t8
UInt64uint64_t8
IntNativessize_tplatform dependent
UIntNativesize_tplatform dependent
Float32float4
Float64double8

对于指针

// c
void* malloc(size_t size);

// cangjie
foreign func malloc(size: UIntNative): CPointer<Unit>

结构体

// c
typedef struct {
    long long x;
    long long y;
    long long z;
} Point3D;

// cangjie
@C
struct Point3D {
    var x: Int64 = 0
    var y: Int64 = 0
    var z: Int64 = 0
}

函数

// c
Point3D addPoint(Point3D p1, Point3D p2);

// cangjie
foreign func addPoint(p1: Point3D, p2: Point3D): Point3D

如果是函数指针:

// Case 1
foreign func free(ptr: CPointer<Int8>): Unit

// Case 2
@C
func callableInC(ptr: CPointer<Int8>) {
    print("This function is defined in Cangjie.")
}

// Case 3
let f1: CFunc<(CPointer<Int8>) -> Unit> = { ptr =>
    print("This function is defined with CFunc lambda.")
}

数组

可以是 CPointer<T> 类型或 VArray<T, $N> 类型。

// c
void cfoo1(int *a) { ... }
void cfoo2(int a[3]) { ... }

// cangjie
foreign func cfoo1(a: CPointer<Int32>): Unit
foreign func cfoo2(a: VArray<Int32, $3>): Unit

结构体数组

// c
struct S {
    int a[2];
    int b[0];
}

// cangjie
@C
struct S {
    var a = VArray<Int32, $2>(repeat: 0)
    var b = VArray<Int32, $0>(repeat: 0)
}

字符串

仓颉中设计了一个 CString 类型来对应。为简化为 C 语言字符串的操作,CString 提供了以下成员函数:

  • init(p: CPointer<UInt8>) 通过 CPointer 构造一个 CString
  • func getChars() 获取字符串的地址,类型为 CPointer<UInt8>
  • func size(): Int64 计算该字符串的长度
  • func isEmpty(): Bool 判断该字符串的长度是否为 0,如果字符串的指针为空返回 true
  • func isNotEmpty(): Bool 判断该字符串的长度是否不为 0,如果字符串的指针为空返回 false
  • func isNull(): Bool 判断该字符串的指针是否为 null
  • func startsWith(str: CString): Bool 判断该字符串是否以 str 开头
  • func endsWith(str: CString): Bool 判断该字符串是否以 str 结尾
  • func equals(rhs: CString): Bool 判断该字符串是否与 rhs 相等
  • func equalsLower(rhs: CString): Bool 判断该字符串是否与 rhs 相等,忽略大小写
  • func subCString(start: UInt64): CString 从 start 开始截取子串,返回的子串存储在新分配的空间中
  • func subCString(start: UInt64, len: UInt64): CString 从 start 开始截取长度为 len 的子串,返回的子串存储在新分配的空间中
  • func compare(str: CString): Int32 该字符串与 str 比较,返回结果与 C 语言的 strcmp(this, str) 一样
  • func toString(): String 用该字符串构造一个新的 String 对象
  • func asResource(): CStringResource 获取 CString 的 Resource 类型

另外,将 String 类型转换为 CString 类型,可以通过调用 LibC 中的 mallocCString 接口,使用完成后需要对 CString 进行释放。

foreign func strlen(s: CString): UIntNative

main() {
    var s1 = unsafe { LibC.mallocCString("hello") }
    var s2 = unsafe { LibC.mallocCString("world") }

    let t1: Int64 = s1.size()
    let t2: Bool = s2.isEmpty()
    let t3: Bool = s1.equals(s2)
    let t4: Bool = s1.startsWith(s2)
    let t5: Int32 = s1.compare(s2)

    let length = unsafe { strlen(s1) }

    unsafe {
        LibC.free(s1)
        LibC.free(s2)
    }
}

C 调用仓颉的函数

在仓颉侧构造 CFunc 类型有两种办法,一个是用 @C 修饰的函数,另外一个是标记为 CFunc 类型的闭包。

其余的与仓颉调用C一样。

仓颉-Python 互操作

目前 Python 互操作仅支持在 Linux 平台使用,并且仅支持仓颉编译器的 cjnative 后端。通过 std 模块中的 ffi.python 库为用户提供能力。通过 std 模块中的 ffi.python 库为用户提供能力。

import std.ffi.python.*
import std.collection.*

main(): Int64 {
    Python.load()

    // Create an unavailable value.
    var a = Python.Eval("a = 10")   // SyntaxError: invalid syntax
    print("${a.isAvailable()}\n")   // false

    // Uncallable value `b` be invoked
    var b = Python.Eval("10")
    b()                           // TypeError: 'int' object is not callable

    // Import .py file.
    var test = Python.Import("test01")

    // `get []` get value of `a`.
    var p_a = test["a"]
    print("${p_a}\n")               // 10

    // `set []` set the value of a to 20.
    test["a"] = Python.Eval("20")
    test["function"]()            // a is 20

    // Call function02 with a named argument.
    test["function02"]([1], HashMap<String, PyObj>([("c", 2.toPyObj())]))

    // Set `a` in test01 to an unavailable value, and `a` will be deleted.
    test["a"] = a
    test["function"]()            // NameError: name 'a' is not defined

    Python.unload()
    0
}

cjpm.toml

[package]
  cjc-version = "0.49.1" # 所需 `cjc` 的最低版本要求,必需
  name = "demo" # 模块名及模块 root 包名,必需
  description = "nothing here" # 描述信息,非必需
  version = "1.0.0" # 模块版本信息,必需
  compile-option = "" # 额外编译命令选项,非必需
  link-option = "" # 链接器透传选项,可透传安全编译命令,非必需
  output-type = "executable" # 编译输出产物类型,必需
  src-dir = "" # 指定源码存放路径,非必需
  target-dir = "" # 指定产物存放路径,非必需
  package-configuration = {} # 单包配置选项,非必需

[workspace] # 工作空间管理字段,与 package 字段不能同时存在
  members = []
  build-members = []
  test-members = []
  compile-option = ""
  link-option = ""
  target-dir = ""

[dependencies] # 源码依赖配置项
  coo = { git = "xxx",branch = "dev" , version = "1.0.0"} # 导入 `git` 依赖,`version`字段可缺省
  doo = { path = "./pro1" ,version = "1.0.0"} # 导入源码依赖,`version`字段可缺省

[test-dependencies] # 测试阶段的依赖配置项

[ffi.c] # 导入 `c` 库依赖
  clib1.path = "xxx"

[profile] # 命令剖面配置项
  build = {}
  test = {}
  customized-option = {}

[target.x86_64-unknown-linux-gnu] # 后端和平台隔离配置项
  compile-option = "value1" # 额外编译命令选项,适用于特定 target 的编译流程和指定该 target 作为交叉编译目标平台的编译流程,非必需
  link-option = "value2" # 链接器透传选项,适用于特定 target 的编译流程和指定该 target 作为交叉编译目标平台的编译流程,非必需

[target.x86_64-w64-mingw32.dependencies] # 适用于对应 target 的源码依赖配置项,非必需

[target.x86_64-w64-mingw32.test-dependencies] # 适用于对应 target 的测试阶段依赖配置项,非必需

[target.cjvm.bin-dependencies] # 仓颉二进制库依赖,适用于特定 target 的编译流程和指定该 target 作为交叉编译目标平台的编译流程,非必需
  path-option = ["./test/pro0", "./test/pro1"]
[target.cjvm.bin-dependencies.package-option]
  "pro0.xoo" = "./test/pro0/pro0.xoo.cjo"
  "pro0.yoo" = "./test/pro0/pro0.yoo.cjo"
  "pro1.zoo" = "./test/pro1/pro1.zoo.cjo"

原文地址:https://blog.csdn.net/bbdxf/article/details/143508405

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!