feat: publish Dever core with project scaffolding and versioned AI skills
This commit is contained in:
1 parent
70fa73cdc2
commit
ac458ffa7f
960 files changed
+221319
-1163
No files matched your search
@@ -0,0 +1,15 @@
|
||||
public round(value: Decimal, places: Int) (result: Decimal) {
|
||||
result = dever.system.decimal_round(value, places)
|
||||
}
|
||||
|
||||
public parse(value: Text) (result: Decimal?) {
|
||||
result = dever.system.decimal_parse(value)
|
||||
}
|
||||
|
||||
public to_text(value: Decimal) (result: Text) {
|
||||
result = dever.system.decimal_to_text(value)
|
||||
}
|
||||
|
||||
public from_int(value: Int) (result: Decimal) {
|
||||
result = dever.system.decimal_from_int(value)
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
public serve() () {
|
||||
dever.system.api_serve()
|
||||
}
|
||||
|
||||
# Upload is a compiler-owned affine POST input. upload_filename, upload_content_type
|
||||
# and upload_size borrow its metadata; close_upload consumes its temporary file.
|
||||
public close_upload(upload: Upload) () {
|
||||
dever.system.upload_close(upload)
|
||||
}
|
||||
|
||||
public type Error {
|
||||
error Invalid
|
||||
error Unauthorized
|
||||
error Forbidden
|
||||
error NotFound
|
||||
error Conflict
|
||||
error TooManyRequests
|
||||
}
|
||||
|
||||
public type SameSite {
|
||||
Strict
|
||||
Lax
|
||||
None
|
||||
}
|
||||
|
||||
public type CookieOptions {
|
||||
path: Text
|
||||
domain: Text?
|
||||
same_site: SameSite
|
||||
secure: Bool
|
||||
http_only: Bool
|
||||
max_age: Int?
|
||||
}
|
||||
|
||||
public secure_cookie_options() (options: CookieOptions) {
|
||||
options = CookieOptions {
|
||||
path = "/"
|
||||
domain = null
|
||||
same_site = SameSite.Lax
|
||||
secure = true
|
||||
http_only = true
|
||||
max_age = null
|
||||
}
|
||||
}
|
||||
|
||||
public request_id() (id: Text) {
|
||||
id = dever.system.api_request_id()
|
||||
}
|
||||
|
||||
public method() (value: Text) {
|
||||
value = dever.system.api_method()
|
||||
}
|
||||
|
||||
public path() (value: Text) {
|
||||
value = dever.system.api_path()
|
||||
}
|
||||
|
||||
public client_address() (value: Text?) {
|
||||
value = dever.system.api_client_address()
|
||||
}
|
||||
|
||||
public header(name: Text) (value: Text?) {
|
||||
value = dever.system.api_header(name)
|
||||
}
|
||||
|
||||
public cookie(name: Text) (value: Text?) {
|
||||
value = dever.system.api_cookie(name)
|
||||
}
|
||||
|
||||
public secret_cookie(name: Text) (value: Secret?) {
|
||||
value = dever.system.api_secret_cookie(name)
|
||||
}
|
||||
|
||||
public set_header(name: Text, value: Text) () {
|
||||
dever.system.api_response_header(name, value)
|
||||
}
|
||||
|
||||
public set_cookie(name: Text, value: Text, options: CookieOptions) () {
|
||||
dever.system.api_response_cookie(name, value, options)
|
||||
}
|
||||
|
||||
public set_secret_cookie(name: Text, value: Secret, options: CookieOptions) () {
|
||||
dever.system.api_response_secret_cookie(name, value, options)
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
public type Claims {
|
||||
subject: Text
|
||||
session: Text
|
||||
tenant: Text?
|
||||
site: Text
|
||||
}
|
||||
|
||||
public type Permission {
|
||||
key: Text
|
||||
component: Text
|
||||
domain: Text
|
||||
site: Text
|
||||
action: Text
|
||||
method: Text
|
||||
}
|
||||
|
||||
public issue(subject: Text, session: Text, tenant: Text?) (token: Secret) {
|
||||
token = dever.system.auth_issue(subject, session, tenant)
|
||||
}
|
||||
|
||||
public issue_cookie(subject: Text, session: Text, tenant: Text?) () {
|
||||
dever.system.auth_issue_cookie(subject, session, tenant)
|
||||
}
|
||||
|
||||
public clear_cookie() () {
|
||||
dever.system.auth_clear_cookie()
|
||||
}
|
||||
|
||||
public id() (value: Text) {
|
||||
value = dever.system.auth_id()
|
||||
}
|
||||
|
||||
public session() (value: Text) {
|
||||
value = dever.system.auth_session()
|
||||
}
|
||||
|
||||
public user_id() (value: Int?) {
|
||||
value = dever.system.auth_user_id()
|
||||
}
|
||||
|
||||
public tenant_id() (value: Int?) {
|
||||
value = dever.system.auth_tenant_id()
|
||||
}
|
||||
|
||||
public permissions() (values: List<Permission>) {
|
||||
values = dever.system.auth_permissions()
|
||||
}
|
||||
|
||||
public save_role(
|
||||
id: Text,
|
||||
name: Text,
|
||||
all_permissions: Bool,
|
||||
permission_keys: List<Text>
|
||||
) () {
|
||||
dever.system.auth_save_role(id, name, all_permissions, permission_keys)
|
||||
}
|
||||
|
||||
public grant_role(user_id: Int, role_id: Text) () {
|
||||
dever.system.auth_grant_role(user_id, role_id)
|
||||
}
|
||||
|
||||
public revoke_role(user_id: Int, role_id: Text) () {
|
||||
dever.system.auth_revoke_role(user_id, role_id)
|
||||
}
|
||||
|
||||
public disable_role(role_id: Text) () {
|
||||
dever.system.auth_disable_role(role_id)
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
public type DecodeResult {
|
||||
Decoded(text: Text)
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
public type BufferResult {
|
||||
Ready(bytes: Bytes)
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
public from_text(text: Text) (bytes: Bytes) {
|
||||
bytes = dever.system.bytes_from_text(text)
|
||||
}
|
||||
|
||||
public to_text(bytes: Bytes) (text: Text) {
|
||||
text = decoded(dever.system.bytes_to_text(bytes))
|
||||
}
|
||||
|
||||
decoded(result: DecodeResult.Decoded(value)) (text: Text) {
|
||||
text = value
|
||||
}
|
||||
|
||||
decoded(result: DecodeResult.Failed(message)) (text: Text) {
|
||||
fail(DecodeResult.Failed(message))
|
||||
}
|
||||
|
||||
public from_ints(values: List<Int>) (bytes: Bytes) {
|
||||
bytes = ready(dever.system.bytes_from_ints(values))
|
||||
}
|
||||
|
||||
public length(bytes: Bytes) (length: Int) {
|
||||
length = dever.system.bytes_length(bytes)
|
||||
}
|
||||
|
||||
public at(bytes: Bytes, index: Int) (value: Int?) {
|
||||
value = dever.system.bytes_at(bytes, index)
|
||||
}
|
||||
|
||||
public slice(bytes: Bytes, start: Int, end: Int) (result: Bytes) {
|
||||
result = ready(dever.system.bytes_slice(bytes, start, end))
|
||||
}
|
||||
|
||||
ready(result: BufferResult.Ready(bytes)) (value: Bytes) {
|
||||
value = bytes
|
||||
}
|
||||
|
||||
ready(result: BufferResult.Failed(message)) (value: Bytes) {
|
||||
fail(BufferResult.Failed(message))
|
||||
}
|
||||
|
||||
public concat(left: Bytes, right: Bytes) (bytes: Bytes) {
|
||||
bytes = dever.system.bytes_concat(left, right)
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
public type TokenResult {
|
||||
Ready(value: Secret)
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
public type HashResult {
|
||||
Ready(value: Text)
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
public type VerifyResult {
|
||||
Ready(value: Bool)
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
public token(bytes: Int) (value: Secret) {
|
||||
value = token_value(dever.system.crypto_token(bytes))
|
||||
}
|
||||
|
||||
token_value(result: TokenResult.Ready(value)) (secret: Secret) {
|
||||
secret = value
|
||||
}
|
||||
|
||||
token_value(result: TokenResult.Failed(message)) (secret: Secret) {
|
||||
fail(TokenResult.Failed(message))
|
||||
}
|
||||
|
||||
public password_hash(password: Secret) (value: Text) {
|
||||
value = hash_value(dever.system.crypto_password_hash(password))
|
||||
}
|
||||
|
||||
hash_value(result: HashResult.Ready(value)) (hash: Text) {
|
||||
hash = value
|
||||
}
|
||||
|
||||
hash_value(result: HashResult.Failed(message)) (hash: Text) {
|
||||
fail(HashResult.Failed(message))
|
||||
}
|
||||
|
||||
public password_verify(password: Secret, hash: Text) (value: Bool) {
|
||||
value = verify_value(dever.system.crypto_password_verify(password, hash))
|
||||
}
|
||||
|
||||
verify_value(result: VerifyResult.Ready(value)) (valid: Bool) {
|
||||
valid = value
|
||||
}
|
||||
|
||||
verify_value(result: VerifyResult.Failed(message)) (valid: Bool) {
|
||||
fail(VerifyResult.Failed(message))
|
||||
}
|
||||
|
||||
public sha256(bytes: Bytes) (value: Bytes) pure {
|
||||
value = dever.system.crypto_sha256(bytes)
|
||||
}
|
||||
|
||||
public hmac_sha256(key: Secret, bytes: Bytes) (value: Bytes) pure {
|
||||
value = dever.system.crypto_hmac_sha256(key, bytes)
|
||||
}
|
||||
|
||||
public constant_time_eq(left: Bytes, right: Bytes) (equal: Bool) pure {
|
||||
equal = dever.system.crypto_constant_time_eq(left, right)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
# Database failures retain their category across services and transactions.
|
||||
public type Error {
|
||||
error Pool(message: Text)
|
||||
error PoolExhausted(message: Text)
|
||||
error Connection(message: Text)
|
||||
error Timeout(message: Text)
|
||||
error Cancelled(message: Text)
|
||||
error Database(message: Text)
|
||||
error Constraint(message: Text)
|
||||
error NotFound(message: Text)
|
||||
error InvalidData(message: Text)
|
||||
error Migration(message: Text)
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
public type PoolLimits {
|
||||
idle_ms: Int
|
||||
chunk_bytes: Int
|
||||
read_ms: Int
|
||||
}
|
||||
|
||||
public type StreamRequest {
|
||||
method: Text
|
||||
target: Text
|
||||
headers: List<Header>
|
||||
body: AsyncStream<dever.io.ReadEvent>
|
||||
}
|
||||
|
||||
public type StreamResponse {
|
||||
status: Int
|
||||
headers: List<Header>
|
||||
body: AsyncStream<dever.io.ReadEvent>
|
||||
}
|
||||
|
||||
public type ClientResult {
|
||||
Ready(client: dever.system.HttpClient)
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
public type StreamResponseResult {
|
||||
Read(response: StreamResponse)
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
public default_pool_limits() (result: PoolLimits) {
|
||||
result = PoolLimits {
|
||||
idle_ms = 10000
|
||||
chunk_bytes = 65536
|
||||
read_ms = 60000
|
||||
}
|
||||
}
|
||||
|
||||
public client(
|
||||
origin: Text,
|
||||
tls: dever.system.ClientTls,
|
||||
limits: Limits,
|
||||
pool: PoolLimits
|
||||
) (client: dever.system.HttpClient) {
|
||||
client = ready_client(dever.system.http_client(origin, tls, limits, pool))
|
||||
}
|
||||
|
||||
ready_client(result: ClientResult.Ready(client)) (value: dever.system.HttpClient) {
|
||||
value = client
|
||||
}
|
||||
|
||||
ready_client(result: ClientResult.Failed(message)) (value: dever.system.HttpClient) {
|
||||
fail(ClientResult.Failed(message))
|
||||
}
|
||||
|
||||
public request(client: dever.system.HttpClient, request: Request) (
|
||||
response: Response
|
||||
) {
|
||||
response = read_response(dever.system.http_request(client, request))
|
||||
}
|
||||
|
||||
public open(client: dever.system.HttpClient, request: Request) (
|
||||
response: StreamResponse
|
||||
) {
|
||||
response = read_stream_response(dever.system.http_open(client, request))
|
||||
}
|
||||
|
||||
public open_stream(client: dever.system.HttpClient, request: StreamRequest) (
|
||||
response: StreamResponse
|
||||
) {
|
||||
response = read_stream_response(dever.system.http_open_stream(client, request))
|
||||
}
|
||||
|
||||
read_stream_response(result: StreamResponseResult.Read(response)) (
|
||||
value: StreamResponse
|
||||
) {
|
||||
value = response
|
||||
}
|
||||
|
||||
read_stream_response(result: StreamResponseResult.Failed(message)) (
|
||||
value: StreamResponse
|
||||
) {
|
||||
fail(StreamResponseResult.Failed(message))
|
||||
}
|
||||
|
||||
public close_client(client: dever.system.HttpClient) () {
|
||||
closed(dever.system.http_close_client(client))
|
||||
}
|
||||
|
||||
public serve_tls(
|
||||
route: handler(request: Request) (response: Response),
|
||||
listener: dever.system.Listener,
|
||||
limits: Limits,
|
||||
tls: dever.system.ServerTls
|
||||
) () {
|
||||
dever.system.http_serve_tls(route, listener, limits, tls)
|
||||
}
|
||||
|
||||
public serve_live_tls(
|
||||
route: handler(request: Request, reply: dever.system.HttpReply) (),
|
||||
listener: dever.system.Listener,
|
||||
limits: Limits,
|
||||
live: LiveLimits,
|
||||
tls: dever.system.ServerTls
|
||||
) () {
|
||||
dever.system.http_serve_live_tls(route, listener, limits, live, tls)
|
||||
}
|
||||
|
||||
public type Header {
|
||||
name: Text
|
||||
value: Bytes
|
||||
}
|
||||
|
||||
public type Request {
|
||||
method: Text
|
||||
target: Text
|
||||
headers: List<Header>
|
||||
body: Bytes
|
||||
}
|
||||
|
||||
public type Response {
|
||||
status: Int
|
||||
headers: List<Header>
|
||||
body: Bytes
|
||||
}
|
||||
|
||||
public type Limits {
|
||||
header_bytes: Int
|
||||
body_bytes: Int
|
||||
timeout_ms: Int
|
||||
connections: Int
|
||||
http2: Http2Limits?
|
||||
}
|
||||
|
||||
# streams 为每连接并发流数;窗口限制协议层未消费的接收正文。
|
||||
public type Http2Limits {
|
||||
streams: Int
|
||||
stream_window_bytes: Int
|
||||
connection_window_bytes: Int
|
||||
}
|
||||
|
||||
public default_http2_limits() (result: Http2Limits) {
|
||||
result = Http2Limits {
|
||||
streams = 16
|
||||
stream_window_bytes = 65535
|
||||
connection_window_bytes = 262144
|
||||
}
|
||||
}
|
||||
|
||||
public type LiveLimits {
|
||||
chunk_bytes: Int
|
||||
idle_ms: Int
|
||||
heartbeat_ms: Int
|
||||
}
|
||||
|
||||
public default_live_limits() (result: LiveLimits) {
|
||||
result = LiveLimits {
|
||||
chunk_bytes = 65536
|
||||
idle_ms = 60000
|
||||
heartbeat_ms = 15000
|
||||
}
|
||||
}
|
||||
|
||||
public type ResponseResult {
|
||||
Read(response: Response)
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
public default_limits() (result: Limits) {
|
||||
result = Limits {
|
||||
header_bytes = 8192
|
||||
body_bytes = 1048576
|
||||
timeout_ms = 5000
|
||||
connections = 32
|
||||
http2 = null
|
||||
}
|
||||
}
|
||||
|
||||
public header(name: Text, value: Text) (result: Header) {
|
||||
result = Header {
|
||||
name = text.lower(name)
|
||||
value = dever.bytes.from_text(value)
|
||||
}
|
||||
}
|
||||
|
||||
# 返回第一个匹配项的原始值;重复头保留在 headers 列表中。
|
||||
public header_value(headers: List<Header>, name: Text) (value: Bytes?) {
|
||||
value = found_header(find(named_header, headers, text.lower(name)))
|
||||
}
|
||||
|
||||
named_header(header: Header, name: Text) (matches: Bool) {
|
||||
matches = text.lower(header.name) == name
|
||||
}
|
||||
|
||||
found_header(header: null) (value: Bytes?) {
|
||||
value = null
|
||||
}
|
||||
|
||||
found_header(header: Header) (value: Bytes?) {
|
||||
value = header.value
|
||||
}
|
||||
|
||||
# serve 是长驻任务,调用者用 run/stop 管理生命周期;故障沿任务作用域传播。
|
||||
public serve(
|
||||
route: handler(request: Request) (response: Response),
|
||||
listener: dever.system.Listener,
|
||||
limits: Limits
|
||||
) () {
|
||||
dever.system.http_serve(route, listener, limits)
|
||||
}
|
||||
|
||||
# 单次请求,无连接池或重试;address 为数字 IP,Host 由请求头指定。
|
||||
public send(address: Text, port: Int, request: Request, limits: Limits) (
|
||||
response: Response
|
||||
) {
|
||||
response = read_response(dever.system.http_send(address, port, request, limits))
|
||||
}
|
||||
|
||||
read_response(result: ResponseResult.Read(response)) (value: Response) {
|
||||
value = response
|
||||
}
|
||||
|
||||
read_response(result: ResponseResult.Failed(message)) (value: Response) {
|
||||
fail(ResponseResult.Failed(message))
|
||||
}
|
||||
|
||||
# handler 可发送完整正文、逐块写入、SSE,或升级 WebSocket;返回即结束会话。
|
||||
public serve_live(
|
||||
route: handler(request: Request, reply: dever.system.HttpReply) (),
|
||||
listener: dever.system.Listener,
|
||||
limits: Limits,
|
||||
live: LiveLimits
|
||||
) () {
|
||||
dever.system.http_serve_live(route, listener, limits, live)
|
||||
}
|
||||
|
||||
public respond(reply: dever.system.HttpReply, response: Response) () {
|
||||
wrote(dever.system.http_respond(reply, response))
|
||||
}
|
||||
|
||||
public start(reply: dever.system.HttpReply, status: Int, headers: List<Header>) () {
|
||||
wrote(dever.system.http_start(reply, status, headers))
|
||||
}
|
||||
|
||||
public write(reply: dever.system.HttpReply, bytes: Bytes) () {
|
||||
wrote(dever.system.http_write(reply, bytes))
|
||||
}
|
||||
|
||||
public finish(reply: dever.system.HttpReply) () {
|
||||
closed(dever.system.http_finish(reply))
|
||||
}
|
||||
|
||||
wrote(result: dever.io.WriteResult.Done) () {}
|
||||
|
||||
wrote(result: dever.io.WriteResult.Failed(message)) () {
|
||||
fail(dever.io.WriteResult.Failed(message))
|
||||
}
|
||||
|
||||
closed(result: dever.io.CloseResult.Closed) () {}
|
||||
|
||||
closed(result: dever.io.CloseResult.Failed(message)) () {
|
||||
fail(dever.io.CloseResult.Failed(message))
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
public from_text(text: Text) (id: Id) {
|
||||
id = dever.system.id_from_text(text)
|
||||
}
|
||||
|
||||
public to_text(id: Id) (text: Text) {
|
||||
text = dever.system.id_to_text(id)
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
public type OpenResult {
|
||||
Opened(file: dever.system.File)
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
public type ReadState {
|
||||
Read(bytes: Bytes)
|
||||
End
|
||||
}
|
||||
|
||||
public type ReadResult {
|
||||
Done(state: ReadState)
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
type SystemReadResult {
|
||||
Read(bytes: Bytes)
|
||||
End
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
public type WriteResult {
|
||||
Done
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
public type CloseResult {
|
||||
Closed
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
public type ReadEvent {
|
||||
Chunk(bytes: Bytes)
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
public type ReadStreamResult {
|
||||
Streaming(stream: Stream<ReadEvent>)
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
public println(text: Text) () {
|
||||
dever.system.stdout_write(text + "\n")
|
||||
}
|
||||
|
||||
public open(path: Text) (file: dever.system.File) {
|
||||
file = opened(dever.system.file_open(path))
|
||||
}
|
||||
|
||||
public create(path: Text) (file: dever.system.File) {
|
||||
file = opened(dever.system.file_create(path))
|
||||
}
|
||||
|
||||
opened(result: OpenResult.Opened(file)) (value: dever.system.File) {
|
||||
value = file
|
||||
}
|
||||
|
||||
opened(result: OpenResult.Failed(message)) (value: dever.system.File) {
|
||||
fail(OpenResult.Failed(message))
|
||||
}
|
||||
|
||||
public read(file: dever.system.File, limit: Int) (state: ReadState) {
|
||||
state = read_state(dever.system.file_read(file, limit))
|
||||
}
|
||||
|
||||
read_state(result: SystemReadResult.Read(bytes)) (state: ReadState) {
|
||||
state = ReadState.Read(bytes)
|
||||
}
|
||||
|
||||
read_state(result: SystemReadResult.End) (state: ReadState) {
|
||||
state = ReadState.End
|
||||
}
|
||||
|
||||
read_state(result: SystemReadResult.Failed(message)) (state: ReadState) {
|
||||
fail(ReadResult.Failed(message))
|
||||
}
|
||||
|
||||
public write(file: dever.system.File, bytes: Bytes) () {
|
||||
wrote(dever.system.file_write(file, bytes))
|
||||
}
|
||||
|
||||
wrote(result: WriteResult.Done) () {}
|
||||
|
||||
wrote(result: WriteResult.Failed(message)) () {
|
||||
fail(WriteResult.Failed(message))
|
||||
}
|
||||
|
||||
public close(file: dever.system.File) () {
|
||||
closed(dever.system.file_close(file))
|
||||
}
|
||||
|
||||
closed(result: CloseResult.Closed) () {}
|
||||
|
||||
closed(result: CloseResult.Failed(message)) () {
|
||||
fail(CloseResult.Failed(message))
|
||||
}
|
||||
|
||||
public chunks(file: dever.system.File, limit: Int) (stream: Stream<ReadEvent>) {
|
||||
stream = read_stream(dever.system.file_chunks(file, limit))
|
||||
}
|
||||
|
||||
read_stream(result: ReadStreamResult.Streaming(stream)) (value: Stream<ReadEvent>) {
|
||||
value = stream
|
||||
}
|
||||
|
||||
read_stream(result: ReadStreamResult.Failed(message)) (value: Stream<ReadEvent>) {
|
||||
fail(ReadStreamResult.Failed(message))
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
public serve() () {
|
||||
dever.system.job_serve()
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
# JSON values and capture result choices live in dever.json.value.
|
||||
public parse(source: Text) (document: dever.json.value.Document) {
|
||||
document = dever.json.parse.parse(source)
|
||||
}
|
||||
|
||||
public stringify(document: dever.json.value.Document) (text: Text) {
|
||||
text = dever.json.write.stringify(document)
|
||||
}
|
||||
|
||||
public node(document: dever.json.value.Document, id: Int) (result: dever.json.value.Value?) {
|
||||
result = get(document.nodes, id)
|
||||
}
|
||||
|
||||
public property(document: dever.json.value.Document, id: Int, name: Text) (result: Int?) {
|
||||
result = object_property(node(document, id), name)
|
||||
}
|
||||
|
||||
object_property(value: dever.json.value.Value.Object(members), name: Text) (
|
||||
result: Int?
|
||||
) {
|
||||
result = get(members, name)
|
||||
}
|
||||
|
||||
object_property(value: null, name: Text) (result: Int?) {
|
||||
result = null
|
||||
}
|
||||
|
||||
object_property(value: other, name: Text) (result: Int?) {
|
||||
result = null
|
||||
}
|
||||
|
||||
type ElementState {
|
||||
target: Int
|
||||
index: Int
|
||||
found: Int?
|
||||
}
|
||||
|
||||
public element(document: dever.json.value.Document, id: Int, index: Int) (result: Int?) {
|
||||
result = array_element(node(document, id), index)
|
||||
}
|
||||
|
||||
array_element(value: dever.json.value.Value.Array(children), index: Int) (result: Int?) {
|
||||
state = reduce_until(
|
||||
element_at,
|
||||
children,
|
||||
ElementState {
|
||||
target = index
|
||||
index = 0
|
||||
found = null
|
||||
}
|
||||
)
|
||||
result = state.found
|
||||
}
|
||||
|
||||
array_element(value: null, index: Int) (result: Int?) {
|
||||
result = null
|
||||
}
|
||||
|
||||
array_element(value: other, index: Int) (result: Int?) {
|
||||
result = null
|
||||
}
|
||||
|
||||
element_at(id: Int, state: ElementState) (next: ElementState, stop: Bool) {
|
||||
next = selected(state.index == state.target, id, state)
|
||||
next.index = state.index + 1
|
||||
stop = state.index >= state.target
|
||||
}
|
||||
|
||||
selected(matches: true, id: Int, state: ElementState) (next: ElementState) {
|
||||
next = state
|
||||
next.found = id
|
||||
}
|
||||
|
||||
selected(matches: false, id: Int, state: ElementState) (next: ElementState) {
|
||||
next = state
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
type Phase {
|
||||
Start
|
||||
Sign
|
||||
Zero
|
||||
Integer
|
||||
Point
|
||||
Fraction
|
||||
Exponent
|
||||
ExponentSign
|
||||
ExponentDigits
|
||||
Invalid
|
||||
}
|
||||
|
||||
public valid(raw: Text) (result: Bool) {
|
||||
phase = reduce(advance, text.split(raw, ""), Phase.Start)
|
||||
result = complete(phase)
|
||||
}
|
||||
|
||||
advance(character: Text, phase: Phase.Start) (result: Phase) {
|
||||
result = start(character)
|
||||
}
|
||||
|
||||
advance(character: Text, phase: Phase.Sign) (result: Phase) {
|
||||
result = integer_start(character)
|
||||
}
|
||||
|
||||
advance(character: Text, phase: Phase.Zero) (result: Phase) {
|
||||
result = after_integer(character)
|
||||
}
|
||||
|
||||
advance(character: Text, phase: Phase.Integer) (result: Phase) {
|
||||
result = integer_digit(text.contains("0123456789", character), character)
|
||||
}
|
||||
|
||||
advance(character: Text, phase: Phase.Point) (result: Phase) {
|
||||
result = digit(text.contains("0123456789", character), Phase.Fraction)
|
||||
}
|
||||
|
||||
advance(character: Text, phase: Phase.Fraction) (result: Phase) {
|
||||
result = fraction_digit(text.contains("0123456789", character), character)
|
||||
}
|
||||
|
||||
advance(character: Text, phase: Phase.Exponent) (result: Phase) {
|
||||
result = exponent_start(character)
|
||||
}
|
||||
|
||||
advance(character: Text, phase: Phase.ExponentSign) (result: Phase) {
|
||||
result = digit(text.contains("0123456789", character), Phase.ExponentDigits)
|
||||
}
|
||||
|
||||
advance(character: Text, phase: Phase.ExponentDigits) (result: Phase) {
|
||||
result = digit(text.contains("0123456789", character), Phase.ExponentDigits)
|
||||
}
|
||||
|
||||
advance(character: Text, phase: Phase.Invalid) (result: Phase) {
|
||||
result = Phase.Invalid
|
||||
}
|
||||
|
||||
start(character: "-") (result: Phase) {
|
||||
result = Phase.Sign
|
||||
}
|
||||
|
||||
start(character: other) (result: Phase) {
|
||||
result = integer_start(character)
|
||||
}
|
||||
|
||||
integer_start(character: "0") (result: Phase) {
|
||||
result = Phase.Zero
|
||||
}
|
||||
|
||||
integer_start(character: other) (result: Phase) {
|
||||
result = digit(text.contains("123456789", character), Phase.Integer)
|
||||
}
|
||||
|
||||
digit(valid: true, phase: Phase) (result: Phase) {
|
||||
result = phase
|
||||
}
|
||||
|
||||
digit(valid: false, phase: Phase) (result: Phase) {
|
||||
result = Phase.Invalid
|
||||
}
|
||||
|
||||
integer_digit(valid: true, character: Text) (result: Phase) {
|
||||
result = Phase.Integer
|
||||
}
|
||||
|
||||
integer_digit(valid: false, character: Text) (result: Phase) {
|
||||
result = after_integer(character)
|
||||
}
|
||||
|
||||
after_integer(character: ".") (result: Phase) {
|
||||
result = Phase.Point
|
||||
}
|
||||
|
||||
after_integer(character: other) (result: Phase) {
|
||||
result = exponent(character)
|
||||
}
|
||||
|
||||
fraction_digit(valid: true, character: Text) (result: Phase) {
|
||||
result = Phase.Fraction
|
||||
}
|
||||
|
||||
fraction_digit(valid: false, character: Text) (result: Phase) {
|
||||
result = exponent(character)
|
||||
}
|
||||
|
||||
exponent(character: "e") (result: Phase) {
|
||||
result = Phase.Exponent
|
||||
}
|
||||
|
||||
exponent(character: "E") (result: Phase) {
|
||||
result = Phase.Exponent
|
||||
}
|
||||
|
||||
exponent(character: other) (result: Phase) {
|
||||
result = Phase.Invalid
|
||||
}
|
||||
|
||||
exponent_start(character: "+") (result: Phase) {
|
||||
result = Phase.ExponentSign
|
||||
}
|
||||
|
||||
exponent_start(character: "-") (result: Phase) {
|
||||
result = Phase.ExponentSign
|
||||
}
|
||||
|
||||
exponent_start(character: other) (result: Phase) {
|
||||
result = digit(text.contains("0123456789", character), Phase.ExponentDigits)
|
||||
}
|
||||
|
||||
complete(phase: Phase.Zero) (result: Bool) {
|
||||
result = true
|
||||
}
|
||||
|
||||
complete(phase: Phase.Integer) (result: Bool) {
|
||||
result = true
|
||||
}
|
||||
|
||||
complete(phase: Phase.Fraction) (result: Bool) {
|
||||
result = true
|
||||
}
|
||||
|
||||
complete(phase: Phase.ExponentDigits) (result: Bool) {
|
||||
result = true
|
||||
}
|
||||
|
||||
complete(phase: other) (result: Bool) {
|
||||
result = false
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
type Expect {
|
||||
RootValue
|
||||
RootDone
|
||||
ArrayFirst
|
||||
ArrayValue
|
||||
ArrayNext
|
||||
ObjectFirst
|
||||
ObjectKey
|
||||
ObjectColon
|
||||
ObjectValue
|
||||
ObjectNext
|
||||
}
|
||||
|
||||
type Frame {
|
||||
expect: Expect
|
||||
children: List<Int>
|
||||
members: Map<Text, Int>
|
||||
key: Text
|
||||
}
|
||||
|
||||
type State {
|
||||
stack: Map<Int, Frame>
|
||||
depth: Int
|
||||
nodes: Map<Int, dever.json.value.Value>
|
||||
next_id: Int
|
||||
root: Int?
|
||||
failure: Text
|
||||
position: Int
|
||||
}
|
||||
|
||||
public parse(source: Text) (document: dever.json.value.Document) {
|
||||
document = scanned(result(dever.json.scan.scan(source)), length(source))
|
||||
}
|
||||
|
||||
scanned(result: dever.json.scan.Result.Scanned(tokens), end: Int) (
|
||||
document: dever.json.value.Document
|
||||
) recover("scanner failures become public JSON parse failures") {
|
||||
state = reduce_until(
|
||||
token,
|
||||
tokens,
|
||||
State {
|
||||
stack = {
|
||||
0 = frame(Expect.RootValue)
|
||||
}
|
||||
depth = 0
|
||||
nodes = {}
|
||||
next_id = 0
|
||||
root = null
|
||||
failure = ""
|
||||
position = 0
|
||||
}
|
||||
)
|
||||
document = finish(state.failure == "", state, end)
|
||||
}
|
||||
|
||||
scanned(result: dever.json.scan.Result.Failed(position, message), end: Int) (
|
||||
document: dever.json.value.Document
|
||||
) recover("scanner failures become public JSON parse failures") {
|
||||
fail(dever.json.value.ParseResult.Failed(position, message))
|
||||
}
|
||||
|
||||
frame(expect: Expect) (result: Frame) {
|
||||
result = Frame {
|
||||
expect = expect
|
||||
children = []
|
||||
members = {}
|
||||
key = ""
|
||||
}
|
||||
}
|
||||
|
||||
finish(valid: false, state: State, end: Int) (document: dever.json.value.Document) {
|
||||
fail(dever.json.value.ParseResult.Failed(state.position, state.failure))
|
||||
}
|
||||
|
||||
finish(valid: true, state: State, end: Int) (document: dever.json.value.Document) {
|
||||
document = balanced(state.depth == 0, state, end)
|
||||
}
|
||||
|
||||
balanced(complete: false, state: State, end: Int) (document: dever.json.value.Document) {
|
||||
fail(dever.json.value.ParseResult.Failed(end, "unterminated JSON container"))
|
||||
}
|
||||
|
||||
balanced(complete: true, state: State, end: Int) (document: dever.json.value.Document) {
|
||||
document = root(state.root, state.nodes, end)
|
||||
}
|
||||
|
||||
root(id: null, nodes: Map<Int, dever.json.value.Value>, end: Int) (
|
||||
document: dever.json.value.Document
|
||||
) {
|
||||
fail(dever.json.value.ParseResult.Failed(end, "expected a JSON value"))
|
||||
}
|
||||
|
||||
root(id: Int, nodes: Map<Int, dever.json.value.Value>, end: Int) (
|
||||
document: dever.json.value.Document
|
||||
) {
|
||||
document = dever.json.value.Document {
|
||||
root = id
|
||||
nodes = nodes
|
||||
}
|
||||
}
|
||||
|
||||
token(token: dever.json.scan.Token, state: State) (next: State, stop: Bool) {
|
||||
positioned = state
|
||||
positioned.position = token.position
|
||||
next = current(get(state.stack, state.depth), token.kind, positioned)
|
||||
stop = next.failure != ""
|
||||
}
|
||||
|
||||
current(frame: null, kind: dever.json.scan.Kind, state: State) (next: State) {
|
||||
next = invalid_state("invalid JSON parser stack", state)
|
||||
}
|
||||
|
||||
current(frame: Frame, kind: dever.json.scan.Kind, state: State) (next: State) {
|
||||
next = expected(frame.expect, kind, frame, state)
|
||||
}
|
||||
|
||||
expected(
|
||||
expect: Expect.RootValue,
|
||||
kind: dever.json.scan.Kind,
|
||||
frame: Frame,
|
||||
state: State
|
||||
) (next: State) {
|
||||
next = value(kind, state)
|
||||
}
|
||||
|
||||
expected(
|
||||
expect: Expect.RootDone,
|
||||
kind: dever.json.scan.Kind,
|
||||
frame: Frame,
|
||||
state: State
|
||||
) (next: State) {
|
||||
next = invalid_state("unexpected content after the JSON value", state)
|
||||
}
|
||||
|
||||
expected(
|
||||
expect: Expect.ArrayFirst,
|
||||
kind: dever.json.scan.Kind,
|
||||
frame: Frame,
|
||||
state: State
|
||||
) (next: State) {
|
||||
next = array_first(kind, frame, state)
|
||||
}
|
||||
|
||||
expected(
|
||||
expect: Expect.ArrayValue,
|
||||
kind: dever.json.scan.Kind,
|
||||
frame: Frame,
|
||||
state: State
|
||||
) (next: State) {
|
||||
next = value(kind, state)
|
||||
}
|
||||
|
||||
expected(
|
||||
expect: Expect.ArrayNext,
|
||||
kind: dever.json.scan.Kind,
|
||||
frame: Frame,
|
||||
state: State
|
||||
) (next: State) {
|
||||
next = array_next(kind, frame, state)
|
||||
}
|
||||
|
||||
expected(
|
||||
expect: Expect.ObjectFirst,
|
||||
kind: dever.json.scan.Kind,
|
||||
frame: Frame,
|
||||
state: State
|
||||
) (next: State) {
|
||||
next = object_first(kind, frame, state)
|
||||
}
|
||||
|
||||
expected(
|
||||
expect: Expect.ObjectKey,
|
||||
kind: dever.json.scan.Kind,
|
||||
frame: Frame,
|
||||
state: State
|
||||
) (next: State) {
|
||||
next = object_key(kind, frame, state)
|
||||
}
|
||||
|
||||
expected(
|
||||
expect: Expect.ObjectColon,
|
||||
kind: dever.json.scan.Kind,
|
||||
frame: Frame,
|
||||
state: State
|
||||
) (next: State) {
|
||||
next = object_colon(kind, frame, state)
|
||||
}
|
||||
|
||||
expected(
|
||||
expect: Expect.ObjectValue,
|
||||
kind: dever.json.scan.Kind,
|
||||
frame: Frame,
|
||||
state: State
|
||||
) (next: State) {
|
||||
next = value(kind, state)
|
||||
}
|
||||
|
||||
expected(
|
||||
expect: Expect.ObjectNext,
|
||||
kind: dever.json.scan.Kind,
|
||||
frame: Frame,
|
||||
state: State
|
||||
) (next: State) {
|
||||
next = object_next(kind, frame, state)
|
||||
}
|
||||
|
||||
value(kind: dever.json.scan.Kind.Scalar(value), state: State) (next: State) {
|
||||
next = commit(value, state)
|
||||
}
|
||||
|
||||
value(kind: dever.json.scan.Kind.ArrayOpen, state: State) (next: State) {
|
||||
next = open(state.depth < dever.json.value.maximum_depth(), Expect.ArrayFirst, state)
|
||||
}
|
||||
|
||||
value(kind: dever.json.scan.Kind.ObjectOpen, state: State) (next: State) {
|
||||
next = open(state.depth < dever.json.value.maximum_depth(), Expect.ObjectFirst, state)
|
||||
}
|
||||
|
||||
value(kind: other, state: State) (next: State) {
|
||||
next = invalid_state("expected a JSON value", state)
|
||||
}
|
||||
|
||||
open(allowed: false, expect: Expect, state: State) (next: State) {
|
||||
next = invalid_state("JSON nesting exceeds 256 containers", state)
|
||||
}
|
||||
|
||||
open(allowed: true, expect: Expect, state: State) (next: State) {
|
||||
next = state
|
||||
next.depth = state.depth + 1
|
||||
next.stack = put(state.stack, next.depth, frame(expect))
|
||||
}
|
||||
|
||||
array_first(kind: dever.json.scan.Kind.ArrayClose, frame: Frame, state: State) (
|
||||
next: State
|
||||
) {
|
||||
next = close(dever.json.value.Value.Array(frame.children), state)
|
||||
}
|
||||
|
||||
array_first(kind: other, frame: Frame, state: State) (next: State) {
|
||||
next = value(kind, state)
|
||||
}
|
||||
|
||||
array_next(kind: dever.json.scan.Kind.ArrayClose, frame: Frame, state: State) (
|
||||
next: State
|
||||
) {
|
||||
next = close(dever.json.value.Value.Array(frame.children), state)
|
||||
}
|
||||
|
||||
array_next(kind: dever.json.scan.Kind.Comma, frame: Frame, state: State) (next: State) {
|
||||
next = expecting(Expect.ArrayValue, frame, state)
|
||||
}
|
||||
|
||||
array_next(kind: other, frame: Frame, state: State) (next: State) {
|
||||
next = invalid_state("expected ',' or ']' after an array element", state)
|
||||
}
|
||||
|
||||
object_first(kind: dever.json.scan.Kind.ObjectClose, frame: Frame, state: State) (
|
||||
next: State
|
||||
) {
|
||||
next = close(dever.json.value.Value.Object(frame.members), state)
|
||||
}
|
||||
|
||||
object_first(kind: other, frame: Frame, state: State) (next: State) {
|
||||
next = object_key(kind, frame, state)
|
||||
}
|
||||
|
||||
object_key(kind: dever.json.scan.Kind.Scalar(value), frame: Frame, state: State) (
|
||||
next: State
|
||||
) {
|
||||
next = key_value(value, frame, state)
|
||||
}
|
||||
|
||||
object_key(kind: other, frame: Frame, state: State) (next: State) {
|
||||
next = invalid_state("expected a quoted JSON object name", state)
|
||||
}
|
||||
|
||||
key_value(value: dever.json.value.Value.String(key), frame: Frame, state: State) (
|
||||
next: State
|
||||
) {
|
||||
next = unique_key(get(frame.members, key) == null, key, frame, state)
|
||||
}
|
||||
|
||||
key_value(value: other, frame: Frame, state: State) (next: State) {
|
||||
next = invalid_state("expected a quoted JSON object name", state)
|
||||
}
|
||||
|
||||
unique_key(unique: false, key: Text, frame: Frame, state: State) (next: State) {
|
||||
next = invalid_state("duplicate JSON object name: " + key, state)
|
||||
}
|
||||
|
||||
unique_key(unique: true, key: Text, frame: Frame, state: State) (next: State) {
|
||||
keyed = frame
|
||||
keyed.key = key
|
||||
next = expecting(Expect.ObjectColon, keyed, state)
|
||||
}
|
||||
|
||||
object_colon(kind: dever.json.scan.Kind.Colon, frame: Frame, state: State) (next: State) {
|
||||
next = expecting(Expect.ObjectValue, frame, state)
|
||||
}
|
||||
|
||||
object_colon(kind: other, frame: Frame, state: State) (next: State) {
|
||||
next = invalid_state("expected ':' after a JSON object name", state)
|
||||
}
|
||||
|
||||
object_next(kind: dever.json.scan.Kind.ObjectClose, frame: Frame, state: State) (
|
||||
next: State
|
||||
) {
|
||||
next = close(dever.json.value.Value.Object(frame.members), state)
|
||||
}
|
||||
|
||||
object_next(kind: dever.json.scan.Kind.Comma, frame: Frame, state: State) (next: State) {
|
||||
next = expecting(Expect.ObjectKey, frame, state)
|
||||
}
|
||||
|
||||
object_next(kind: other, frame: Frame, state: State) (next: State) {
|
||||
next = invalid_state("expected ',' or '}' after an object member", state)
|
||||
}
|
||||
|
||||
expecting(expect: Expect, frame: Frame, state: State) (next: State) {
|
||||
updated = frame
|
||||
updated.expect = expect
|
||||
next = state
|
||||
next.stack = put(state.stack, state.depth, updated)
|
||||
}
|
||||
|
||||
close(value: dever.json.value.Value, state: State) (next: State) {
|
||||
parent = state
|
||||
parent.stack = remove(state.stack, state.depth)
|
||||
parent.depth = state.depth - 1
|
||||
next = commit(value, parent)
|
||||
}
|
||||
|
||||
commit(value: dever.json.value.Value, state: State) (next: State) {
|
||||
inserted = state
|
||||
inserted.nodes = put(state.nodes, state.next_id, value)
|
||||
inserted.next_id = state.next_id + 1
|
||||
next = attach(get(state.stack, state.depth), state.next_id, inserted)
|
||||
}
|
||||
|
||||
attach(frame: null, id: Int, state: State) (next: State) {
|
||||
next = invalid_state("invalid JSON parser parent", state)
|
||||
}
|
||||
|
||||
attach(frame: Frame, id: Int, state: State) (next: State) {
|
||||
next = attach_expected(frame.expect, frame, id, state)
|
||||
}
|
||||
|
||||
attach_expected(expect: Expect.RootValue, frame: Frame, id: Int, state: State) (
|
||||
next: State
|
||||
) {
|
||||
next = expecting(Expect.RootDone, frame, state)
|
||||
next.root = id
|
||||
}
|
||||
|
||||
attach_expected(expect: Expect.ArrayFirst, frame: Frame, id: Int, state: State) (
|
||||
next: State
|
||||
) {
|
||||
next = append_child(frame, id, state)
|
||||
}
|
||||
|
||||
attach_expected(expect: Expect.ArrayValue, frame: Frame, id: Int, state: State) (
|
||||
next: State
|
||||
) {
|
||||
next = append_child(frame, id, state)
|
||||
}
|
||||
|
||||
attach_expected(expect: Expect.ObjectValue, frame: Frame, id: Int, state: State) (
|
||||
next: State
|
||||
) {
|
||||
updated = frame
|
||||
updated.members = put(frame.members, frame.key, id)
|
||||
next = expecting(Expect.ObjectNext, updated, state)
|
||||
}
|
||||
|
||||
attach_expected(expect: other, frame: Frame, id: Int, state: State) (next: State) {
|
||||
next = invalid_state("unexpected JSON value", state)
|
||||
}
|
||||
|
||||
append_child(frame: Frame, id: Int, state: State) (next: State) {
|
||||
updated = frame
|
||||
updated.children = append(frame.children, id)
|
||||
next = expecting(Expect.ArrayNext, updated, state)
|
||||
}
|
||||
|
||||
invalid_state(message: Text, state: State) (next: State) {
|
||||
next = state
|
||||
next.failure = message
|
||||
}
|
||||
@@ -0,0 +1,442 @@
|
||||
public type Kind {
|
||||
Scalar(value: dever.json.value.Value)
|
||||
ObjectOpen
|
||||
ObjectClose
|
||||
ArrayOpen
|
||||
ArrayClose
|
||||
Colon
|
||||
Comma
|
||||
}
|
||||
|
||||
public type Token {
|
||||
kind: Kind
|
||||
position: Int
|
||||
}
|
||||
|
||||
public type Result {
|
||||
Scanned(tokens: List<Token>)
|
||||
error Failed(position: Int, message: Text)
|
||||
}
|
||||
|
||||
type Mode {
|
||||
Normal
|
||||
Word
|
||||
String
|
||||
Escape
|
||||
Unicode
|
||||
LowSlash
|
||||
LowU
|
||||
}
|
||||
|
||||
type State {
|
||||
tokens: List<Token>
|
||||
mode: Mode
|
||||
buffer: Text
|
||||
position: Int
|
||||
start: Int
|
||||
unicode: Int
|
||||
digits: Int
|
||||
high: Int
|
||||
failure: Text
|
||||
error_position: Int
|
||||
}
|
||||
|
||||
public scan(source: Text) (tokens: List<Token>) {
|
||||
state = reduce_until(
|
||||
character,
|
||||
text.split(source + " ", ""),
|
||||
State {
|
||||
tokens = []
|
||||
mode = Mode.Normal
|
||||
buffer = ""
|
||||
position = 0
|
||||
start = 0
|
||||
unicode = 0
|
||||
digits = 0
|
||||
high = 0
|
||||
failure = ""
|
||||
error_position = 0
|
||||
}
|
||||
)
|
||||
tokens = finish(state.failure == "", state, length(source))
|
||||
}
|
||||
|
||||
finish(valid: false, state: State, end: Int) (tokens: List<Token>) {
|
||||
fail(Result.Failed(state.error_position, state.failure))
|
||||
}
|
||||
|
||||
finish(valid: true, state: State, end: Int) (tokens: List<Token>) {
|
||||
tokens = finish_mode(state.mode, state.tokens, end)
|
||||
}
|
||||
|
||||
finish_mode(mode: Mode.Normal, tokens: List<Token>, end: Int) (result: List<Token>) {
|
||||
result = tokens
|
||||
}
|
||||
|
||||
finish_mode(mode: other, tokens: List<Token>, end: Int) (result: List<Token>) {
|
||||
fail(Result.Failed(end, "unterminated JSON string or escape"))
|
||||
}
|
||||
|
||||
character(value: Text, state: State) (next: State, stop: Bool) {
|
||||
next = consume(state.mode, value, state)
|
||||
next.position = state.position + 1
|
||||
stop = next.failure != ""
|
||||
}
|
||||
|
||||
consume(mode: Mode.Normal, value: Text, state: State) (next: State) {
|
||||
next = normal(value, state)
|
||||
}
|
||||
|
||||
consume(mode: Mode.Word, value: Text, state: State) (next: State) {
|
||||
next = word(text.contains(" \t\r\n{}[]:,\"", value), value, state)
|
||||
}
|
||||
|
||||
consume(mode: Mode.String, value: Text, state: State) (next: State) {
|
||||
next = string(value, state)
|
||||
}
|
||||
|
||||
consume(mode: Mode.Escape, value: Text, state: State) (next: State) {
|
||||
next = escape(value, state)
|
||||
}
|
||||
|
||||
consume(mode: Mode.Unicode, value: Text, state: State) (next: State) {
|
||||
next = unicode_digit(text.index_of("0123456789abcdef", text.lower(value)), state)
|
||||
}
|
||||
|
||||
consume(mode: Mode.LowSlash, value: Text, state: State) (next: State) {
|
||||
next = low_slash(value == "\\", state)
|
||||
}
|
||||
|
||||
consume(mode: Mode.LowU, value: Text, state: State) (next: State) {
|
||||
next = low_u(value == "u", state)
|
||||
}
|
||||
|
||||
normal(value: "{", state: State) (next: State) {
|
||||
next = emit(Kind.ObjectOpen, state.position, state)
|
||||
}
|
||||
|
||||
normal(value: "}", state: State) (next: State) {
|
||||
next = emit(Kind.ObjectClose, state.position, state)
|
||||
}
|
||||
|
||||
normal(value: "[", state: State) (next: State) {
|
||||
next = emit(Kind.ArrayOpen, state.position, state)
|
||||
}
|
||||
|
||||
normal(value: "]", state: State) (next: State) {
|
||||
next = emit(Kind.ArrayClose, state.position, state)
|
||||
}
|
||||
|
||||
normal(value: ":", state: State) (next: State) {
|
||||
next = emit(Kind.Colon, state.position, state)
|
||||
}
|
||||
|
||||
normal(value: ",", state: State) (next: State) {
|
||||
next = emit(Kind.Comma, state.position, state)
|
||||
}
|
||||
|
||||
normal(value: "\"", state: State) (next: State) {
|
||||
next = state
|
||||
next.mode = Mode.String
|
||||
next.start = state.position
|
||||
next.buffer = ""
|
||||
}
|
||||
|
||||
normal(value: other, state: State) (next: State) {
|
||||
next = whitespace(text.contains(" \t\r\n", value), value, state)
|
||||
}
|
||||
|
||||
whitespace(space: true, value: Text, state: State) (next: State) {
|
||||
next = state
|
||||
}
|
||||
|
||||
whitespace(space: false, value: Text, state: State) (next: State) {
|
||||
next = state
|
||||
next.mode = Mode.Word
|
||||
next.start = state.position
|
||||
next.buffer = value
|
||||
}
|
||||
|
||||
word(ended: false, value: Text, state: State) (next: State) {
|
||||
next = state
|
||||
next.buffer = state.buffer + value
|
||||
}
|
||||
|
||||
word(ended: true, value: Text, state: State) (next: State) {
|
||||
finished = atom(state.buffer, state)
|
||||
next = after_word(finished.failure == "", value, finished)
|
||||
}
|
||||
|
||||
after_word(valid: true, value: Text, state: State) (next: State) {
|
||||
next = normal(value, state)
|
||||
}
|
||||
|
||||
after_word(valid: false, value: Text, state: State) (next: State) {
|
||||
next = state
|
||||
}
|
||||
|
||||
atom(raw: "true", state: State) (next: State) {
|
||||
next = scalar(dever.json.value.Value.Boolean(true), state)
|
||||
}
|
||||
|
||||
atom(raw: "false", state: State) (next: State) {
|
||||
next = scalar(dever.json.value.Value.Boolean(false), state)
|
||||
}
|
||||
|
||||
atom(raw: "null", state: State) (next: State) {
|
||||
next = scalar(dever.json.value.Value.Null, state)
|
||||
}
|
||||
|
||||
atom(raw: other, state: State) (next: State) {
|
||||
next = number(dever.json.number.valid(raw), raw, state)
|
||||
}
|
||||
|
||||
number(valid: true, raw: Text, state: State) (next: State) {
|
||||
next = scalar(dever.json.value.Value.Number(raw), state)
|
||||
}
|
||||
|
||||
number(valid: false, raw: Text, state: State) (next: State) {
|
||||
next = invalid_state(state.start, "invalid JSON literal or number", state)
|
||||
}
|
||||
|
||||
scalar(value: dever.json.value.Value, state: State) (next: State) {
|
||||
next = emit(Kind.Scalar(value), state.start, state)
|
||||
next.mode = Mode.Normal
|
||||
}
|
||||
|
||||
emit(kind: Kind, position: Int, state: State) (next: State) {
|
||||
next = state
|
||||
next.tokens = append(
|
||||
state.tokens,
|
||||
Token {
|
||||
kind = kind
|
||||
position = position
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
invalid_state(position: Int, message: Text, state: State) (next: State) {
|
||||
next = state
|
||||
next.failure = message
|
||||
next.error_position = position
|
||||
}
|
||||
|
||||
string(value: "\"", state: State) (next: State) {
|
||||
next = scalar(dever.json.value.Value.String(state.buffer), state)
|
||||
}
|
||||
|
||||
string(value: "\\", state: State) (next: State) {
|
||||
next = state
|
||||
next.mode = Mode.Escape
|
||||
}
|
||||
|
||||
string(value: other, state: State) (next: State) {
|
||||
next = string_character(text.codepoint(value), value, state)
|
||||
}
|
||||
|
||||
string_character(code: Int, value: Text, state: State) (next: State) {
|
||||
next = string_control(code < 32, value, state)
|
||||
}
|
||||
|
||||
string_character(code: null, value: Text, state: State) (next: State) {
|
||||
next = invalid_state(state.position, "invalid Unicode character", state)
|
||||
}
|
||||
|
||||
string_control(control: true, value: Text, state: State) (next: State) {
|
||||
next = invalid_state(state.position, "unescaped control character in JSON string", state)
|
||||
}
|
||||
|
||||
string_control(control: false, value: Text, state: State) (next: State) {
|
||||
next = decoded(value, state)
|
||||
}
|
||||
|
||||
decoded(value: Text, state: State) (next: State) {
|
||||
next = state
|
||||
next.buffer = state.buffer + value
|
||||
next.mode = Mode.String
|
||||
}
|
||||
|
||||
escape(value: "\"", state: State) (next: State) {
|
||||
next = decoded("\"", state)
|
||||
}
|
||||
|
||||
escape(value: "\\", state: State) (next: State) {
|
||||
next = decoded("\\", state)
|
||||
}
|
||||
|
||||
escape(value: "/", state: State) (next: State) {
|
||||
next = decoded("/", state)
|
||||
}
|
||||
|
||||
escape(value: "b", state: State) (next: State) {
|
||||
next = decoded("\u{8}", state)
|
||||
}
|
||||
|
||||
escape(value: "f", state: State) (next: State) {
|
||||
next = decoded("\u{c}", state)
|
||||
}
|
||||
|
||||
escape(value: "n", state: State) (next: State) {
|
||||
next = decoded("\n", state)
|
||||
}
|
||||
|
||||
escape(value: "r", state: State) (next: State) {
|
||||
next = decoded("\r", state)
|
||||
}
|
||||
|
||||
escape(value: "t", state: State) (next: State) {
|
||||
next = decoded("\t", state)
|
||||
}
|
||||
|
||||
escape(value: "u", state: State) (next: State) {
|
||||
next = state
|
||||
next.mode = Mode.Unicode
|
||||
next.unicode = 0
|
||||
next.digits = 0
|
||||
}
|
||||
|
||||
escape(value: other, state: State) (next: State) {
|
||||
next = invalid_state(state.position, "invalid JSON string escape", state)
|
||||
}
|
||||
|
||||
unicode_digit(digit: null, state: State) (next: State) {
|
||||
next = invalid_state(state.position, "expected four hexadecimal escape digits", state)
|
||||
}
|
||||
|
||||
unicode_digit(digit: Int, state: State) (next: State) {
|
||||
advanced = state
|
||||
advanced.unicode = state.unicode * 16 + digit
|
||||
advanced.digits = state.digits + 1
|
||||
next = unicode_ready(advanced.digits == 4, advanced)
|
||||
}
|
||||
|
||||
unicode_ready(ready: false, state: State) (next: State) {
|
||||
next = state
|
||||
}
|
||||
|
||||
unicode_ready(ready: true, state: State) (next: State) {
|
||||
next = unicode_unit(state.high == 0, state.unicode, state)
|
||||
}
|
||||
|
||||
unicode_unit(first: true, code: Int, state: State) (next: State) {
|
||||
next = first_unit(code, state)
|
||||
}
|
||||
|
||||
unicode_unit(first: false, code: Int, state: State) (next: State) {
|
||||
next = second_unit(code >= 56320 and code <= 57343, code, state)
|
||||
}
|
||||
|
||||
first_unit(code: Int >= 55296 and <= 56319, state: State) (next: State) {
|
||||
next = state
|
||||
next.high = code
|
||||
next.mode = Mode.LowSlash
|
||||
}
|
||||
|
||||
first_unit(code: Int >= 56320 and <= 57343, state: State) (next: State) {
|
||||
next = invalid_state(state.position, "unpaired low Unicode surrogate", state)
|
||||
}
|
||||
|
||||
first_unit(code: other, state: State) (next: State) {
|
||||
next = scalar_character(text.from_codepoint(code), state)
|
||||
}
|
||||
|
||||
second_unit(valid: false, code: Int, state: State) (next: State) {
|
||||
next = invalid_state(state.position, "expected a low Unicode surrogate", state)
|
||||
}
|
||||
|
||||
second_unit(valid: true, code: Int, state: State) (next: State) {
|
||||
next = scalar_character(
|
||||
text.from_codepoint(65536 + (state.high - 55296) * 1024 + code - 56320),
|
||||
state
|
||||
)
|
||||
}
|
||||
|
||||
scalar_character(value: Text, state: State) (next: State) {
|
||||
next = decoded(value, state)
|
||||
next.high = 0
|
||||
}
|
||||
|
||||
scalar_character(value: null, state: State) (next: State) {
|
||||
next = invalid_state(state.position, "invalid Unicode scalar", state)
|
||||
}
|
||||
|
||||
low_slash(valid: true, state: State) (next: State) {
|
||||
next = state
|
||||
next.mode = Mode.LowU
|
||||
}
|
||||
|
||||
low_slash(valid: false, state: State) (next: State) {
|
||||
next = invalid_state(state.position, "expected a low Unicode surrogate escape", state)
|
||||
}
|
||||
|
||||
low_u(valid: true, state: State) (next: State) {
|
||||
next = escape("u", state)
|
||||
}
|
||||
|
||||
low_u(valid: false, state: State) (next: State) {
|
||||
next = invalid_state(state.position, "expected a low Unicode surrogate escape", state)
|
||||
}
|
||||
|
||||
public quote(value: Text) (result: Text) {
|
||||
result = "\"" + text.join(each(quoted_character, text.split(value, "")), "") + "\""
|
||||
}
|
||||
|
||||
quoted_character(value: "\"") (result: Text) {
|
||||
result = "\\\""
|
||||
}
|
||||
|
||||
quoted_character(value: "\\") (result: Text) {
|
||||
result = "\\\\"
|
||||
}
|
||||
|
||||
quoted_character(value: "\u{8}") (result: Text) {
|
||||
result = "\\b"
|
||||
}
|
||||
|
||||
quoted_character(value: "\u{c}") (result: Text) {
|
||||
result = "\\f"
|
||||
}
|
||||
|
||||
quoted_character(value: "\n") (result: Text) {
|
||||
result = "\\n"
|
||||
}
|
||||
|
||||
quoted_character(value: "\r") (result: Text) {
|
||||
result = "\\r"
|
||||
}
|
||||
|
||||
quoted_character(value: "\t") (result: Text) {
|
||||
result = "\\t"
|
||||
}
|
||||
|
||||
quoted_character(value: other) (result: Text) {
|
||||
result = quoted_code(text.codepoint(value), value)
|
||||
}
|
||||
|
||||
quoted_code(code: Int, value: Text) (result: Text) {
|
||||
result = quote_control(code < 32, code, value)
|
||||
}
|
||||
|
||||
quoted_code(code: null, value: Text) (result: Text) {
|
||||
result = value
|
||||
}
|
||||
|
||||
quote_control(control: false, code: Int, value: Text) (result: Text) {
|
||||
result = value
|
||||
}
|
||||
|
||||
quote_control(control: true, code: Int, value: Text) (result: Text) {
|
||||
result = "\\u00" + hex_digit(code // 16) + hex_digit(code % 16)
|
||||
}
|
||||
|
||||
hex_digit(value: Int) (result: Text) {
|
||||
result = hex_character(text.at("0123456789abcdef", value))
|
||||
}
|
||||
|
||||
hex_character(value: Text) (result: Text) {
|
||||
result = value
|
||||
}
|
||||
|
||||
hex_character(value: null) (result: Text) {
|
||||
result = ""
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
# Nodes use consecutive postorder IDs starting at zero. Children precede their parent.
|
||||
public type Value {
|
||||
Null
|
||||
Boolean(value: Bool)
|
||||
Number(raw: Text)
|
||||
String(text: Text)
|
||||
Array(children: List<Int>)
|
||||
Object(members: Map<Text, Int>)
|
||||
}
|
||||
|
||||
public type Document {
|
||||
root: Int
|
||||
nodes: Map<Int, Value>
|
||||
}
|
||||
|
||||
# Parse positions are zero-based Unicode scalar offsets, including the EOF position.
|
||||
public type ParseResult {
|
||||
Parsed(document: Document)
|
||||
error Failed(position: Int, message: Text)
|
||||
}
|
||||
|
||||
public type WriteResult {
|
||||
Written(text: Text)
|
||||
error Failed(node: Int, message: Text)
|
||||
}
|
||||
|
||||
public maximum_depth() (result: Int) {
|
||||
result = 256
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
type Encoded {
|
||||
text: Text
|
||||
depth: Int
|
||||
failure: Text
|
||||
}
|
||||
|
||||
type State {
|
||||
document: dever.json.value.Document
|
||||
encoded: Map<Int, Encoded>
|
||||
index: Int
|
||||
failure: Text
|
||||
}
|
||||
|
||||
type Children {
|
||||
encoded: Map<Int, Encoded>
|
||||
parent: Int
|
||||
pieces: List<Text>
|
||||
depth: Int
|
||||
size: Int
|
||||
failure: Text
|
||||
}
|
||||
|
||||
# The output budget also bounds expansion of user-constructed shared child graphs.
|
||||
maximum_size() (result: Int) {
|
||||
result = 16777216
|
||||
}
|
||||
|
||||
public stringify(document: dever.json.value.Document) (text: Text) {
|
||||
state = reduce_until(
|
||||
node,
|
||||
entries(document.nodes),
|
||||
State {
|
||||
document = document
|
||||
encoded = {}
|
||||
index = 0
|
||||
failure = ""
|
||||
}
|
||||
)
|
||||
text = finish(state.failure == "", state)
|
||||
}
|
||||
|
||||
finish(valid: false, state: State) (text: Text) {
|
||||
fail(dever.json.value.WriteResult.Failed(state.index, state.failure))
|
||||
}
|
||||
|
||||
finish(valid: true, state: State) (text: Text) {
|
||||
text = root(get(state.encoded, state.document.root), state.document.root)
|
||||
}
|
||||
|
||||
root(value: null, id: Int) (text: Text) {
|
||||
fail(
|
||||
dever.json.value.WriteResult.Failed(
|
||||
id,
|
||||
"JSON root does not refer to a document node"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
root(value: Encoded, id: Int) (text: Text) {
|
||||
text = value.text
|
||||
}
|
||||
|
||||
# Iteration supplies a count; lookup uses IDs, so Map insertion order is immaterial.
|
||||
node(entry: MapEntry<Int, dever.json.value.Value>, state: State) (
|
||||
next: State,
|
||||
stop: Bool
|
||||
) {
|
||||
encoded = document_node(get(state.document.nodes, state.index), state)
|
||||
next = store(encoded.failure == "", encoded, state)
|
||||
stop = next.failure != ""
|
||||
}
|
||||
|
||||
document_node(value: null, state: State) (result: Encoded) {
|
||||
result = failed("JSON node IDs must be consecutive from zero")
|
||||
}
|
||||
|
||||
document_node(value: dever.json.value.Value, state: State) (result: Encoded) {
|
||||
encoded = encode(value, state)
|
||||
result = bounded(length(encoded.text) <= maximum_size(), encoded)
|
||||
}
|
||||
|
||||
store(valid: false, encoded: Encoded, state: State) (next: State) {
|
||||
next = state
|
||||
next.failure = encoded.failure
|
||||
}
|
||||
|
||||
store(valid: true, encoded: Encoded, state: State) (next: State) {
|
||||
next = state
|
||||
next.encoded = put(state.encoded, state.index, encoded)
|
||||
next.index = state.index + 1
|
||||
}
|
||||
|
||||
bounded(valid: true, encoded: Encoded) (result: Encoded) {
|
||||
result = encoded
|
||||
}
|
||||
|
||||
bounded(valid: false, encoded: Encoded) (result: Encoded) {
|
||||
result = failed("JSON output exceeds 16777216 Unicode scalars")
|
||||
}
|
||||
|
||||
encoded(text: Text) (result: Encoded) {
|
||||
result = Encoded {
|
||||
text = text
|
||||
depth = 0
|
||||
failure = ""
|
||||
}
|
||||
}
|
||||
|
||||
failed(message: Text) (result: Encoded) {
|
||||
result = Encoded {
|
||||
text = ""
|
||||
depth = 0
|
||||
failure = message
|
||||
}
|
||||
}
|
||||
|
||||
encode(value: dever.json.value.Value.Null, state: State) (result: Encoded) {
|
||||
result = encoded("null")
|
||||
}
|
||||
|
||||
encode(value: dever.json.value.Value.Boolean(boolean), state: State) (result: Encoded) {
|
||||
result = encoded(boolean_text(boolean))
|
||||
}
|
||||
|
||||
encode(value: dever.json.value.Value.Number(raw), state: State) (result: Encoded) {
|
||||
result = number(dever.json.number.valid(raw), raw)
|
||||
}
|
||||
|
||||
encode(value: dever.json.value.Value.String(string), state: State) (result: Encoded) {
|
||||
result = encoded(dever.json.scan.quote(string))
|
||||
}
|
||||
|
||||
encode(value: dever.json.value.Value.Array(children), state: State) (result: Encoded) {
|
||||
built = reduce_until(array_child, children, new_children(state))
|
||||
result = container(built.failure == "", "[", "]", built)
|
||||
}
|
||||
|
||||
encode(value: dever.json.value.Value.Object(members), state: State) (result: Encoded) {
|
||||
built = reduce_until(object_child, entries(members), new_children(state))
|
||||
result = container(built.failure == "", "{", "}", built)
|
||||
}
|
||||
|
||||
boolean_text(value: true) (result: Text) {
|
||||
result = "true"
|
||||
}
|
||||
|
||||
boolean_text(value: false) (result: Text) {
|
||||
result = "false"
|
||||
}
|
||||
|
||||
number(valid: true, raw: Text) (result: Encoded) {
|
||||
result = encoded(raw)
|
||||
}
|
||||
|
||||
number(valid: false, raw: Text) (result: Encoded) {
|
||||
result = failed("invalid JSON number spelling")
|
||||
}
|
||||
|
||||
new_children(state: State) (result: Children) {
|
||||
result = Children {
|
||||
encoded = state.encoded
|
||||
parent = state.index
|
||||
pieces = []
|
||||
depth = 1
|
||||
size = 2
|
||||
failure = ""
|
||||
}
|
||||
}
|
||||
|
||||
array_child(id: Int, state: Children) (next: Children, stop: Bool) {
|
||||
next = child(id >= 0 and id < state.parent, id, "", state)
|
||||
stop = next.failure != ""
|
||||
}
|
||||
|
||||
object_child(member: MapEntry<Text, Int>, state: Children) (next: Children, stop: Bool) {
|
||||
next = child(
|
||||
member.value >= 0 and member.value < state.parent,
|
||||
member.value,
|
||||
dever.json.scan.quote(member.key) + ":",
|
||||
state
|
||||
)
|
||||
stop = next.failure != ""
|
||||
}
|
||||
|
||||
child(valid: false, id: Int, prefix: Text, state: Children) (next: Children) {
|
||||
next = child_failure("JSON child references must precede their parent", state)
|
||||
}
|
||||
|
||||
child(valid: true, id: Int, prefix: Text, state: Children) (next: Children) {
|
||||
next = child_value(get(state.encoded, id), prefix, state)
|
||||
}
|
||||
|
||||
child_value(value: null, prefix: Text, state: Children) (next: Children) {
|
||||
next = child_failure("JSON child node is missing", state)
|
||||
}
|
||||
|
||||
child_value(value: Encoded, prefix: Text, state: Children) (next: Children) {
|
||||
size = state.size + length(prefix) + length(value.text) + separator_size(
|
||||
length(state.pieces) > 0
|
||||
)
|
||||
next = child_size(size <= maximum_size(), size, value, prefix, state)
|
||||
}
|
||||
|
||||
separator_size(needed: true) (result: Int) {
|
||||
result = 1
|
||||
}
|
||||
|
||||
separator_size(needed: false) (result: Int) {
|
||||
result = 0
|
||||
}
|
||||
|
||||
child_size(valid: false, size: Int, value: Encoded, prefix: Text, state: Children) (
|
||||
next: Children
|
||||
) {
|
||||
next = child_failure("JSON output exceeds 16777216 Unicode scalars", state)
|
||||
}
|
||||
|
||||
child_size(valid: true, size: Int, value: Encoded, prefix: Text, state: Children) (
|
||||
next: Children
|
||||
) {
|
||||
next = state
|
||||
next.pieces = append(state.pieces, prefix + value.text)
|
||||
next.depth = maximum(state.depth, value.depth + 1)
|
||||
next.size = size
|
||||
}
|
||||
|
||||
maximum(left: Int, right: Int) (result: Int) {
|
||||
result = larger(left >= right, left, right)
|
||||
}
|
||||
|
||||
larger(left_larger: true, left: Int, right: Int) (result: Int) {
|
||||
result = left
|
||||
}
|
||||
|
||||
larger(left_larger: false, left: Int, right: Int) (result: Int) {
|
||||
result = right
|
||||
}
|
||||
|
||||
child_failure(message: Text, state: Children) (next: Children) {
|
||||
next = state
|
||||
next.failure = message
|
||||
}
|
||||
|
||||
container(valid: false, open: Text, close: Text, state: Children) (result: Encoded) {
|
||||
result = failed(state.failure)
|
||||
}
|
||||
|
||||
container(valid: true, open: Text, close: Text, state: Children) (result: Encoded) {
|
||||
result = container_depth(
|
||||
state.depth <= dever.json.value.maximum_depth(),
|
||||
open,
|
||||
close,
|
||||
state
|
||||
)
|
||||
}
|
||||
|
||||
container_depth(valid: false, open: Text, close: Text, state: Children) (
|
||||
result: Encoded
|
||||
) {
|
||||
result = failed("JSON nesting exceeds 256 containers")
|
||||
}
|
||||
|
||||
container_depth(valid: true, open: Text, close: Text, state: Children) (result: Encoded) {
|
||||
result = Encoded {
|
||||
text = open + text.join(state.pieces, ",") + close
|
||||
depth = state.depth
|
||||
failure = ""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
public debug(message: Text, fields: Map<Text, Text>) () {
|
||||
dever.system.log_debug(message, fields)
|
||||
}
|
||||
|
||||
public info(message: Text, fields: Map<Text, Text>) () {
|
||||
dever.system.log_info(message, fields)
|
||||
}
|
||||
|
||||
public warn(message: Text, fields: Map<Text, Text>) () {
|
||||
dever.system.log_warn(message, fields)
|
||||
}
|
||||
|
||||
public error(message: Text, fields: Map<Text, Text>) () {
|
||||
dever.system.log_error(message, fields)
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
public type ConnectResult {
|
||||
Connected(socket: dever.system.Socket)
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
public type ListenResult {
|
||||
Listening(listener: dever.system.Listener)
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
public type PortResult {
|
||||
Bound(port: Int)
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
public type ReadStreamResult {
|
||||
Streaming(stream: AsyncStream<dever.io.ReadEvent>)
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
type SystemReadResult {
|
||||
Read(bytes: Bytes)
|
||||
End
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
public connect(host: Text, port: Int) (socket: dever.system.Socket) {
|
||||
socket = connected(dever.system.tcp_connect(host, port))
|
||||
}
|
||||
|
||||
# 有界连接使用数值 IP,避免把 DNS 解析误称为受此超时控制。
|
||||
public connect_timeout(address: Text, port: Int, milliseconds: Int) (socket: dever.system.Socket) {
|
||||
socket = connected(dever.system.tcp_connect_timeout(address, port, milliseconds))
|
||||
}
|
||||
|
||||
public accept(listener: dever.system.Listener) (socket: dever.system.Socket) {
|
||||
socket = connected(dever.system.tcp_accept(listener))
|
||||
}
|
||||
|
||||
connected(result: ConnectResult.Connected(socket)) (value: dever.system.Socket) {
|
||||
value = socket
|
||||
}
|
||||
|
||||
connected(result: ConnectResult.Failed(message)) (value: dever.system.Socket) {
|
||||
fail(ConnectResult.Failed(message))
|
||||
}
|
||||
|
||||
public listen(host: Text, port: Int) (listener: dever.system.Listener) {
|
||||
listener = listening(dever.system.tcp_listen(host, port))
|
||||
}
|
||||
|
||||
listening(result: ListenResult.Listening(listener)) (value: dever.system.Listener) {
|
||||
value = listener
|
||||
}
|
||||
|
||||
listening(result: ListenResult.Failed(message)) (value: dever.system.Listener) {
|
||||
fail(ListenResult.Failed(message))
|
||||
}
|
||||
|
||||
public port(listener: dever.system.Listener) (port: Int) {
|
||||
port = bound(dever.system.tcp_port(listener))
|
||||
}
|
||||
|
||||
bound(result: PortResult.Bound(port)) (value: Int) {
|
||||
value = port
|
||||
}
|
||||
|
||||
bound(result: PortResult.Failed(message)) (value: Int) {
|
||||
fail(PortResult.Failed(message))
|
||||
}
|
||||
|
||||
public read(socket: dever.system.Socket, limit: Int) (state: dever.io.ReadState) {
|
||||
state = read_state(dever.system.tcp_read(socket, limit))
|
||||
}
|
||||
|
||||
read_state(result: SystemReadResult.Read(bytes)) (state: dever.io.ReadState) {
|
||||
state = dever.io.ReadState.Read(bytes)
|
||||
}
|
||||
|
||||
read_state(result: SystemReadResult.End) (state: dever.io.ReadState) {
|
||||
state = dever.io.ReadState.End
|
||||
}
|
||||
|
||||
read_state(result: SystemReadResult.Failed(message)) (state: dever.io.ReadState) {
|
||||
fail(dever.io.ReadResult.Failed(message))
|
||||
}
|
||||
|
||||
public write(socket: dever.system.Socket, bytes: Bytes) () {
|
||||
wrote(dever.system.tcp_write(socket, bytes))
|
||||
}
|
||||
|
||||
public timeout(socket: dever.system.Socket, milliseconds: Int) () {
|
||||
wrote(dever.system.tcp_timeout(socket, milliseconds))
|
||||
}
|
||||
|
||||
wrote(result: dever.io.WriteResult.Done) () {}
|
||||
|
||||
wrote(result: dever.io.WriteResult.Failed(message)) () {
|
||||
fail(dever.io.WriteResult.Failed(message))
|
||||
}
|
||||
|
||||
public close(socket: dever.system.Socket) () {
|
||||
closed(dever.system.tcp_close(socket))
|
||||
}
|
||||
|
||||
public close_listener(listener: dever.system.Listener) () {
|
||||
closed(dever.system.tcp_close_listener(listener))
|
||||
}
|
||||
|
||||
closed(result: dever.io.CloseResult.Closed) () {}
|
||||
|
||||
closed(result: dever.io.CloseResult.Failed(message)) () {
|
||||
fail(dever.io.CloseResult.Failed(message))
|
||||
}
|
||||
|
||||
public chunks(socket: dever.system.Socket, limit: Int) (stream: AsyncStream<dever.io.ReadEvent>) {
|
||||
stream = read_stream(dever.system.tcp_chunks(socket, limit))
|
||||
}
|
||||
|
||||
read_stream(result: ReadStreamResult.Streaming(stream)) (
|
||||
value: AsyncStream<dever.io.ReadEvent>
|
||||
) {
|
||||
value = stream
|
||||
}
|
||||
|
||||
read_stream(result: ReadStreamResult.Failed(message)) (
|
||||
value: AsyncStream<dever.io.ReadEvent>
|
||||
) {
|
||||
fail(ReadStreamResult.Failed(message))
|
||||
}
|
||||
|
||||
public connections(listener: dever.system.Listener) (result: AsyncStream<ConnectResult>) {
|
||||
result = dever.system.tcp_connections(listener)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
public type Output {
|
||||
code: Int
|
||||
stdout: Bytes
|
||||
stderr: Bytes
|
||||
}
|
||||
|
||||
public type Error {
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
public type ArgumentsResult {
|
||||
Read(values: List<Text>)
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
public arguments() (values: List<Text>) {
|
||||
values = arguments_value(dever.system.process_arguments())
|
||||
}
|
||||
|
||||
arguments_value(result: ArgumentsResult.Read(values)) (value: List<Text>) {
|
||||
value = values
|
||||
}
|
||||
|
||||
arguments_value(result: ArgumentsResult.Failed(message)) (value: List<Text>) {
|
||||
fail(ArgumentsResult.Failed(message))
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
public key() (value: Text) {
|
||||
value = dever.system.site_key()
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
public type Event {
|
||||
event: Text
|
||||
data: Text
|
||||
id: Text?
|
||||
retry_ms: Int?
|
||||
}
|
||||
|
||||
# 自动输出 Content-Type、默认 no-cache 和空闲心跳;重连位置由 Request 头读取。
|
||||
public start(reply: dever.system.HttpReply, headers: List<dever.http.Header>) () {
|
||||
wrote(dever.system.sse_start(reply, headers))
|
||||
}
|
||||
|
||||
public send(reply: dever.system.HttpReply, event: Event) () {
|
||||
wrote(dever.system.sse_send(reply, event))
|
||||
}
|
||||
|
||||
wrote(result: dever.io.WriteResult.Done) () {}
|
||||
|
||||
wrote(result: dever.io.WriteResult.Failed(message)) () {
|
||||
fail(dever.io.WriteResult.Failed(message))
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
public type PutResult {
|
||||
Stored(key: Uuid)
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
public put(upload: Upload) (key: Uuid) {
|
||||
key = stored(dever.system.upload_store(upload))
|
||||
}
|
||||
|
||||
stored(result: PutResult.Stored(key)) (value: Uuid) {
|
||||
value = key
|
||||
}
|
||||
|
||||
stored(result: PutResult.Failed(message)) (value: Uuid) {
|
||||
fail(PutResult.Failed(message))
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
# 第一个 tick 在一个周期后;落后的周期跳过,不积压待处理 tick。
|
||||
public ticks(millis: Int) (values: AsyncStream<Int>) {
|
||||
values = dever.system.task_ticks(millis)
|
||||
}
|
||||
|
||||
public sleep(millis: Int) () {
|
||||
slept(dever.system.task_sleep(millis))
|
||||
}
|
||||
|
||||
slept(result: dever.time.SleepResult.Done) () {}
|
||||
|
||||
slept(result: dever.time.SleepResult.Failed(message)) () {
|
||||
fail(dever.time.SleepResult.Failed(message))
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
public type ClockResult {
|
||||
Read(value: Int)
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
public type SleepResult {
|
||||
Done
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
public unix_millis() (value: Int) {
|
||||
value = clock_value(dever.system.time_unix_millis())
|
||||
}
|
||||
|
||||
public monotonic_nanos() (value: Int) {
|
||||
value = clock_value(dever.system.time_monotonic_nanos())
|
||||
}
|
||||
|
||||
clock_value(result: ClockResult.Read(value)) (clock: Int) {
|
||||
clock = value
|
||||
}
|
||||
|
||||
clock_value(result: ClockResult.Failed(message)) (clock: Int) {
|
||||
fail(ClockResult.Failed(message))
|
||||
}
|
||||
|
||||
public sleep(millis: Int) () {
|
||||
slept(dever.system.time_sleep(millis))
|
||||
}
|
||||
|
||||
slept(result: SleepResult.Done) () {}
|
||||
|
||||
slept(result: SleepResult.Failed(message)) () {
|
||||
fail(SleepResult.Failed(message))
|
||||
}
|
||||
|
||||
public type DateTimeResult {
|
||||
Read(value: DateTime)
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
datetime_value(result: DateTimeResult.Read(value)) (output: DateTime) pure {
|
||||
output = value
|
||||
}
|
||||
|
||||
datetime_value(result: DateTimeResult.Failed(message)) (output: DateTime) pure {
|
||||
fail(DateTimeResult.Failed(message))
|
||||
}
|
||||
|
||||
public type DateResult {
|
||||
Read(value: Date)
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
date_value(result: DateResult.Read(value)) (output: Date) pure {
|
||||
output = value
|
||||
}
|
||||
|
||||
date_value(result: DateResult.Failed(message)) (output: Date) pure {
|
||||
fail(DateResult.Failed(message))
|
||||
}
|
||||
|
||||
public type TimeResult {
|
||||
Read(value: Time)
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
time_value(result: TimeResult.Read(value)) (output: Time) pure {
|
||||
output = value
|
||||
}
|
||||
|
||||
time_value(result: TimeResult.Failed(message)) (output: Time) pure {
|
||||
fail(TimeResult.Failed(message))
|
||||
}
|
||||
|
||||
public type DurationResult {
|
||||
Read(value: Duration)
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
duration_value(result: DurationResult.Read(value)) (output: Duration) pure {
|
||||
output = value
|
||||
}
|
||||
|
||||
duration_value(result: DurationResult.Failed(message)) (output: Duration) pure {
|
||||
fail(DurationResult.Failed(message))
|
||||
}
|
||||
|
||||
public type TextResult {
|
||||
Read(value: Text)
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
text_value(result: TextResult.Read(value)) (output: Text) pure {
|
||||
output = value
|
||||
}
|
||||
|
||||
text_value(result: TextResult.Failed(message)) (output: Text) pure {
|
||||
fail(TextResult.Failed(message))
|
||||
}
|
||||
|
||||
public now() (output: DateTime) {
|
||||
output = datetime_value(dever.system.time_now())
|
||||
}
|
||||
|
||||
public parse_datetime(text: Text) (output: DateTime) pure {
|
||||
output = datetime_value(dever.system.time_parse_datetime(text))
|
||||
}
|
||||
|
||||
public format_datetime(value: DateTime) (output: Text) pure {
|
||||
output = text_value(dever.system.time_format_datetime(value))
|
||||
}
|
||||
|
||||
public parse_date(text: Text) (output: Date) pure {
|
||||
output = date_value(dever.system.time_parse_date(text))
|
||||
}
|
||||
|
||||
public format_date(value: Date) (output: Text) pure {
|
||||
output = text_value(dever.system.time_format_date(value))
|
||||
}
|
||||
|
||||
public parse_time(text: Text) (output: Time) pure {
|
||||
output = time_value(dever.system.time_parse_time(text))
|
||||
}
|
||||
|
||||
public format_time(value: Time) (output: Text) pure {
|
||||
output = text_value(dever.system.time_format_time(value))
|
||||
}
|
||||
|
||||
public add(value: DateTime, duration: Duration) (output: DateTime) pure {
|
||||
output = datetime_value(dever.system.time_add(value, duration))
|
||||
}
|
||||
|
||||
public subtract(value: DateTime, duration: Duration) (output: DateTime) pure {
|
||||
output = datetime_value(dever.system.time_subtract(value, duration))
|
||||
}
|
||||
|
||||
public difference(later: DateTime, earlier: DateTime) (output: Duration) pure {
|
||||
output = duration_value(dever.system.time_difference(later, earlier))
|
||||
}
|
||||
|
||||
public duration(millis: Int) (output: Duration) pure {
|
||||
output = dever.system.time_duration(millis)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
public type ClientResult {
|
||||
Ready(config: dever.system.ClientTls)
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
public type ServerResult {
|
||||
Ready(config: dever.system.ServerTls)
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
# 内置公共 CA;校验证书链、有效期和连接目标名称。
|
||||
public system() (config: dever.system.ClientTls) {
|
||||
config = dever.system.tls_system()
|
||||
}
|
||||
|
||||
# 仅信任给定 PEM CA;不提供跳过校验的模式。
|
||||
public client(ca_pem: Bytes) (config: dever.system.ClientTls) {
|
||||
config = client_config(dever.system.tls_client(ca_pem))
|
||||
}
|
||||
|
||||
client_config(result: ClientResult.Ready(config)) (value: dever.system.ClientTls) {
|
||||
value = config
|
||||
}
|
||||
|
||||
client_config(result: ClientResult.Failed(message)) (value: dever.system.ClientTls) {
|
||||
fail(ClientResult.Failed(message))
|
||||
}
|
||||
|
||||
public server(cert_pem: Bytes, key_pem: Bytes) (config: dever.system.ServerTls) {
|
||||
config = server_config(dever.system.tls_server(cert_pem, key_pem))
|
||||
}
|
||||
|
||||
server_config(result: ServerResult.Ready(config)) (value: dever.system.ServerTls) {
|
||||
value = config
|
||||
}
|
||||
|
||||
server_config(result: ServerResult.Failed(message)) (value: dever.system.ServerTls) {
|
||||
fail(ServerResult.Failed(message))
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
public open(url: Text, tls: dever.system.ClientTls, limits: Limits) (
|
||||
socket: dever.system.WebSocket
|
||||
) {
|
||||
socket = connected(dever.system.ws_open(url, tls, limits))
|
||||
}
|
||||
|
||||
public type Limits {
|
||||
message_bytes: Int
|
||||
idle_ms: Int
|
||||
write_ms: Int
|
||||
}
|
||||
|
||||
public type Message {
|
||||
Text(value: Text)
|
||||
Binary(value: Bytes)
|
||||
Ping(value: Bytes)
|
||||
Pong(value: Bytes)
|
||||
}
|
||||
|
||||
public type ConnectResult {
|
||||
Connected(socket: dever.system.WebSocket)
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
public type ReceiveState {
|
||||
Read(message: Message)
|
||||
End
|
||||
}
|
||||
|
||||
public type ReceiveResult {
|
||||
Done(state: ReceiveState)
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
type SystemReceiveResult {
|
||||
Read(message: Message)
|
||||
End
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
public type MessageEvent {
|
||||
Read(message: Message)
|
||||
error Failed(message: Text)
|
||||
}
|
||||
|
||||
public default_limits() (result: Limits) {
|
||||
result = Limits {
|
||||
message_bytes = 1048576
|
||||
idle_ms = 60000
|
||||
write_ms = 5000
|
||||
}
|
||||
}
|
||||
|
||||
public accept(reply: dever.system.HttpReply, limits: Limits) (socket: dever.system.WebSocket) {
|
||||
socket = connected(dever.system.ws_accept(reply, limits))
|
||||
}
|
||||
|
||||
public connect(address: Text, port: Int, target: Text, limits: Limits) (
|
||||
socket: dever.system.WebSocket
|
||||
) {
|
||||
socket = connected(dever.system.ws_connect(address, port, target, limits))
|
||||
}
|
||||
|
||||
connected(result: ConnectResult.Connected(socket)) (value: dever.system.WebSocket) {
|
||||
value = socket
|
||||
}
|
||||
|
||||
connected(result: ConnectResult.Failed(message)) (value: dever.system.WebSocket) {
|
||||
fail(ConnectResult.Failed(message))
|
||||
}
|
||||
|
||||
public send(socket: dever.system.WebSocket, message: Message) () {
|
||||
wrote(dever.system.ws_send(socket, message))
|
||||
}
|
||||
|
||||
wrote(result: dever.io.WriteResult.Done) () {}
|
||||
|
||||
wrote(result: dever.io.WriteResult.Failed(message)) () {
|
||||
fail(dever.io.WriteResult.Failed(message))
|
||||
}
|
||||
|
||||
public receive(socket: dever.system.WebSocket) (state: ReceiveState) {
|
||||
state = receive_state(dever.system.ws_receive(socket))
|
||||
}
|
||||
|
||||
receive_state(result: SystemReceiveResult.Read(message)) (state: ReceiveState) {
|
||||
state = ReceiveState.Read(message)
|
||||
}
|
||||
|
||||
receive_state(result: SystemReceiveResult.End) (state: ReceiveState) {
|
||||
state = ReceiveState.End
|
||||
}
|
||||
|
||||
receive_state(result: SystemReceiveResult.Failed(message)) (state: ReceiveState) {
|
||||
fail(ReceiveResult.Failed(message))
|
||||
}
|
||||
|
||||
# 拉取时处理协议控制帧;自然关闭结束序列,协议故障产生最后一个 Failed。
|
||||
public messages(socket: dever.system.WebSocket) (result: AsyncStream<MessageEvent>) {
|
||||
result = dever.system.ws_messages(socket)
|
||||
}
|
||||
|
||||
public close(socket: dever.system.WebSocket, code: Int, reason: Text) () {
|
||||
closed(dever.system.ws_close(socket, code, reason))
|
||||
}
|
||||
|
||||
closed(result: dever.io.CloseResult.Closed) () {}
|
||||
|
||||
closed(result: dever.io.CloseResult.Failed(message)) () {
|
||||
fail(dever.io.CloseResult.Failed(message))
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
public from_int(value: Int) (result: Float) {
|
||||
result = dever.system.float_from_int(value)
|
||||
}
|
||||
|
||||
public parse(value: Text) (result: Float?) {
|
||||
result = dever.system.float_parse(value)
|
||||
}
|
||||
|
||||
public to_text(value: Float) (result: Text) {
|
||||
result = dever.system.float_to_text(value)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
public parse(value: Text) (result: Int?) {
|
||||
result = dever.system.int_parse(value)
|
||||
}
|
||||
|
||||
public to_text(value: Int) (result: Text) {
|
||||
result = dever.system.int_to_text(value)
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
public sqrt(value: Float) (result: Float) {
|
||||
result = dever.system.float_sqrt(value)
|
||||
}
|
||||
|
||||
public sin(value: Float) (result: Float) {
|
||||
result = dever.system.float_sin(value)
|
||||
}
|
||||
|
||||
public cos(value: Float) (result: Float) {
|
||||
result = dever.system.float_cos(value)
|
||||
}
|
||||
|
||||
public log(value: Float) (result: Float) {
|
||||
result = dever.system.float_log(value)
|
||||
}
|
||||
|
||||
public pow(value: Float, exponent: Float) (result: Float) {
|
||||
result = dever.system.float_pow(value, exponent)
|
||||
}
|
||||
|
||||
public is_nan(value: Float) (result: Bool) {
|
||||
result = dever.system.float_is_nan(value)
|
||||
}
|
||||
|
||||
public is_finite(value: Float) (result: Bool) {
|
||||
result = dever.system.float_is_finite(value)
|
||||
}
|
||||
|
||||
public is_infinite(value: Float) (result: Bool) {
|
||||
result = dever.system.float_is_infinite(value)
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
public trim(value: Text) (result: Text) {
|
||||
result = dever.system.text_trim(value)
|
||||
}
|
||||
|
||||
public lower(value: Text) (result: Text) {
|
||||
result = dever.system.text_lower(value)
|
||||
}
|
||||
|
||||
public upper(value: Text) (result: Text) {
|
||||
result = dever.system.text_upper(value)
|
||||
}
|
||||
|
||||
public codepoint(value: Text) (result: Int?) {
|
||||
result = dever.system.text_codepoint(value)
|
||||
}
|
||||
|
||||
public from_codepoint(value: Int) (result: Text?) {
|
||||
result = dever.system.text_from_codepoint(value)
|
||||
}
|
||||
|
||||
public at(value: Text, index: Int) (result: Text?) {
|
||||
result = dever.system.text_at(value, index)
|
||||
}
|
||||
|
||||
public slice(value: Text, start: Int, end: Int) (result: Text?) {
|
||||
result = dever.system.text_slice(value, start, end)
|
||||
}
|
||||
|
||||
public index_of(value: Text, pattern: Text) (result: Int?) {
|
||||
result = dever.system.text_index_of(value, pattern)
|
||||
}
|
||||
|
||||
public contains(value: Text, pattern: Text) (result: Bool) {
|
||||
result = dever.system.text_contains(value, pattern)
|
||||
}
|
||||
|
||||
public starts_with(value: Text, prefix: Text) (result: Bool) {
|
||||
result = dever.system.text_starts_with(value, prefix)
|
||||
}
|
||||
|
||||
public ends_with(value: Text, suffix: Text) (result: Bool) {
|
||||
result = dever.system.text_ends_with(value, suffix)
|
||||
}
|
||||
|
||||
public split(value: Text, separator: Text) (result: List<Text>) {
|
||||
result = dever.system.text_split(value, separator)
|
||||
}
|
||||
|
||||
public replace(value: Text, pattern: Text, replacement: Text) (result: Text) {
|
||||
result = dever.system.text_replace(value, pattern, replacement)
|
||||
}
|
||||
|
||||
public is_empty(value: Text) (result: Bool) {
|
||||
result = length(value) == 0
|
||||
}
|
||||
|
||||
type JoinState {
|
||||
text: Text
|
||||
separator: Text
|
||||
first: Bool
|
||||
}
|
||||
|
||||
public join(values: List<Text>, separator: Text) (result: Text) {
|
||||
state = reduce(
|
||||
join_piece,
|
||||
values,
|
||||
JoinState {
|
||||
text = ""
|
||||
separator = separator
|
||||
first = true
|
||||
}
|
||||
)
|
||||
result = state.text
|
||||
}
|
||||
|
||||
join_piece(value: Text, state: JoinState) (result: JoinState) {
|
||||
result = join_next(state.first, value, state)
|
||||
}
|
||||
|
||||
join_next(first: true, value: Text, state: JoinState) (result: JoinState) {
|
||||
result = state
|
||||
result.text = value
|
||||
result.first = false
|
||||
}
|
||||
|
||||
join_next(first: false, value: Text, state: JoinState) (result: JoinState) {
|
||||
result = state
|
||||
result.text = state.text + state.separator + value
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
public parse(value: Text) (result: Uuid?) {
|
||||
result = dever.system.uuid_parse(value)
|
||||
}
|
||||
|
||||
public to_text(value: Uuid) (result: Text) {
|
||||
result = dever.system.uuid_to_text(value)
|
||||
}
|
||||
Reference in new issue
Block a user