/*
* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
* This source file is part of the Cangjie project, licensed under Apache-2.0
* with Runtime Library Exception.
*
* See https://cangjie-lang.cn/pages/LICENSE for license information.
*/
// The Cangjie API is in Beta. For details on its capabilities and limitations, please refer to the README file.
/**
* @file
*
* This is a class for consoleReader.
*/
package std.env
import std.io.InputStream
import std.sync.*
const defaultCapacity: Int64 = 32
/**
* This class Provides the ability to read data from console
*/
public class ConsoleReader <: InputStream {
private static let mutex: Mutex = Mutex()
private var buffer: Array<Byte> = Array<Byte>(defaultCapacity, repeat: 0) /* stdin read buffer */
private var endIndex: Int64 = 0
private var startIndex: Int64 = 0
init() {}
/**
* Read one char
* @throws IllegalMemoryException if there are some system errors.
*/
@OverflowWrapping
public func read(): ?Rune {
synchronized(mutex) {
if (endIndex == startIndex && !readFromConsole()) {
return None
}
let (rune, size): (Rune, Int64) = Rune.fromUtf8(buffer, startIndex)
startIndex += size
return rune
}
}
/** Read one line */
public func readln(): ?String {
if (let Some(line) <- readUntil(r'\n')) {
// not including CR character
let size = line.size
if (line.endsWith("\r\n")) {
return line[..size - 2]
} else if (line.endsWith('\n')) {
return line[..size - 1]
}
return line
}
return None
}
/** Read all line */
public func readToEnd(): ?String {
return readUntil({_ => false})
}
/**
* Reads until some contents is encountered, or the end of the stream is reached.
* ch is included in the result
*/
public func readUntil(ch: Rune): ?String {
return readUntil({it => it == ch})
}
/**
* Reads until predicate is encountered, or the end of the stream is reached.
* ch is included in the result
*/
public func readUntil(predicate: (Rune) -> Bool): ?String {
let result: StringBuilder = StringBuilder()
synchronized(mutex) {
while (true) {
if (startIndex >= endIndex && !readFromConsole()) {
break
}
if (let Some(byteOffset) <- findFirstOf(predicate)) {
unsafe { result.appendFromUtf8Unchecked(buffer[startIndex..byteOffset]) }
startIndex = byteOffset
break
}
unsafe { result.appendFromUtf8Unchecked(buffer[startIndex..endIndex]) }
startIndex = this.endIndex
}
}
return if (result.size == 0) {
None
} else {
result.toString()
}
}
/*
* write Array<Byte> from stdIn to arr and return the length
*/
public func read(arr: Array<Byte>): Int64 {
let arraySize = arr.size
synchronized(mutex) {
while (arraySize > endIndex - startIndex) {
if (!readFromConsole()) {
buffer.copyTo(arr, startIndex, 0, endIndex - startIndex)
let ret = endIndex - startIndex
startIndex = 0
endIndex = 0
return ret
}
}
buffer.copyTo(arr, startIndex, 0, arraySize)
startIndex += arraySize
}
return arraySize
}
private func readFromConsole(): Bool {
let line: CPointer<StructureString> = unsafe { CJ_CONSOLE_Readline() }
if (line.isNull()) {
unsafe { CJ_CONSOLE_ClearstdInerr() }
return false
}
unsafe {
let ss = line.read()
try {
this.append(ss)
} finally {
LibC.free(ss.buffer)
LibC.free(line)
}
}
return true
}
/*
* @returns utf8 position
*/
private func findFirstOf(predicate: (Rune) -> Bool): ?Int64 {
var byteOffset = startIndex
while (byteOffset < endIndex) {
let (char, len): (Rune, Int64) = Rune.fromUtf8(buffer, byteOffset)
byteOffset += len
if (predicate(char)) {
return byteOffset
}
}
return None
}
@OverflowWrapping
unsafe func append(str: StructureString): Unit {
let itemLen = Int64(str.length)
this.reserve(itemLen)
if (itemLen < 32) {
for (i in 0..itemLen) {
this.buffer[i + endIndex] = str.buffer.read(i)
}
this.endIndex += itemLen
return
}
let data: CPointerHandle<UInt8> = acquireArrayRawData(this.buffer)
let rc = memcpy_s(data.pointer + endIndex, UIntNative(buffer.size - endIndex), str.buffer,
UIntNative(itemLen))
releaseArrayRawData(data)
if (rc != 0) {
// never reaches here
throw IllegalMemoryException("The memcpy_s failed with error code: ${rc}.")
}
this.endIndex += itemLen
}
@OverflowThrowing
private func reserve(addition: Int64): Unit {
// |---------|------|---------|
// 0 start end size-1
let rightRemain = this.buffer.size - this.endIndex
// if have enough space, do nothing
if (rightRemain >= addition) {
return
}
// if move data to head can make space, move data to head
if (this.startIndex >= addition - rightRemain) {
this.buffer.copyTo(this.buffer, startIndex, 0, this.endIndex - startIndex)
this.endIndex -= startIndex
this.startIndex = 0
return
}
this.grow(this.buffer.size + addition)
}
@OverflowWrapping
private func grow(minCapacity: Int64): Unit {
let oldCapacity = this.buffer.size
var newCapacity = oldCapacity + (oldCapacity >> 1)
if (newCapacity < minCapacity) {
newCapacity = minCapacity
}
var itemData = Array<UInt8>(newCapacity, repeat: 0)
this.buffer.copyTo(itemData, this.startIndex, 0, this.endIndex - this.startIndex)
this.buffer = itemData
this.endIndex = this.endIndex - this.startIndex
this.startIndex = 0
}
}