nandi/frqpublic Fork 0
d333b6f7b4be024f494b613751b18edbeed856bc
Commits
Clone
git clone https://git.rickub.com/nandi/frq.git
git clone ssh://git@rickub.com/nandi/frq.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

frq_core.nim · 139 lines · 5.5 KBNim Blame HistoryRaw
One frontend where there were three, and a core that is not Clojure 438b247 nandi 15h ago1## The C ABI, and nothing else.
2##
3## Every exported symbol is here so there is one file to read when asking what
4## the Dart side can call. The logic lives under `frq/` in ordinary Nim with
5## ordinary Nim types, which is what lets the tests test the rules rather than
6## the marshalling.
7##
8## Two conventions, both of which the bindings in `flutter/src/frq/core/`
9## wrap so no call site has to remember them:
10##
11## * Every returned string is the **caller's** to free, with `frq_free`. Nim's
12## allocator is not Dart's, so a `free()` on this side of the boundary is
13## undefined behaviour rather than a leak you can live with.
14## * `frq_init` runs once before anything else. Nim's runtime needs setting up
15## and `--app:lib` does not do it for you on every platform.
16##
17## Answers that are not a single string come back as JSON. A struct would mean
18## both sides agreeing on a memory layout, and a field added later would be a
19## version skew that segfaults rather than one that fails; JSON costs a parse
20## per call, which is nothing against the network round trip that produced the
21## line being parsed.
Delete the spike that owned the screens 1bb3f77 nandi 14h ago22##
23## There was a second half to this ABI once — `frq_ui_render`, `frq_ui_dispatch`
24## and a state machine and screens behind them — from an experiment where Nim
25## owned the UI as well. It is gone: it meant reimplementing screens that
26## already exist and are far better, and the seam that actually wanted Nim
27## under it was `frq.net`.
One frontend where there were three, and a core that is not Clojure 438b247 nandi 15h ago28
29import std/json
Delete the spike that owned the screens 1bb3f77 nandi 14h ago30import frq/[ircparse, trace]
Nim under the existing UI, not instead of it 56551a8 nandi 14h ago31import frq/conn as tr
One frontend where there were three, and a core that is not Clojure 438b247 nandi 15h ago32
33proc NimMain() {.importc.}
34
35var initialised = false
36
37proc frq_init*() {.exportc, dynlib.} =
38 ## Set Nim's runtime up. Idempotent, because a binding that guesses wrong
39 ## about whether it has been called should be harmless rather than fatal.
40 if not initialised:
41 NimMain()
42 initialised = true
43
44proc dup(s: string): cstring =
45 ## A copy of `s` that outlives this call, for the caller to `frq_free`.
46 ## `allocShared0` and not `alloc0`: the Dart side may free it from a
47 ## different thread than the one that made it.
48 let n = s.len
49 let p = cast[cstring](allocShared0(n + 1))
50 if n > 0:
51 copyMem(p, unsafeAddr s[0], n)
52 p
53
54proc frq_free*(p: cstring) {.exportc, dynlib.} =
55 ## Free what one of the functions below returned. Null is fine.
56 if p != nil:
57 deallocShared(p)
58
59proc frq_version*(): cstring {.exportc, dynlib.} =
60 ## Static storage, deliberately: this one is NOT freed, and is the only
61 ## exception to the rule above. It exists so a binding can check at load
62 ## time that the library it found is the one it was built against.
63 "0.1.0"
64
65# ----------------------------------------------------------------- irc/parse
66
67proc frq_irc_parse_line*(line: cstring): cstring {.exportc, dynlib.} =
68 ## An IRC line as JSON: `{raw, tags, account, prefix, command, params}`.
69 ##
70 ## `tags`, `account` and `prefix` are JSON null where the line carried none,
71 ## which is the distinction `frq.irc.parse` draws with nil and every caller
72 ## of it depends on — a PRIVMSG from a server with no prefix is not the same
73 ## line as one from a nick.
74 if line == nil: return dup("null")
75 let p = parseLine($line)
76 var o = newJObject()
77 o["raw"] = %p.raw
78 o["tags"] = if p.hasTags: %p.tags else: newJNull()
79 o["account"] = if p.hasAccount: %p.account else: newJNull()
80 o["prefix"] = if p.hasPrefix: %p.prefix else: newJNull()
81 o["command"] = %p.command
82 o["params"] = %p.params
83 dup($o)
84
85proc frq_irc_tag_value*(tags, key: cstring): cstring {.exportc, dynlib.} =
86 ## One tag's value, unescaped — or **null** where the tag is absent or
87 ## empty, which IRCv3 says are the same thing. Null and not "" on purpose:
88 ## see `tagValue`.
89 if tags == nil or key == nil: return nil
90 let (v, ok) = tagValue($tags, $key)
91 if ok: dup(v) else: nil
92
93proc frq_irc_unescape_tag*(v: cstring): cstring {.exportc, dynlib.} =
94 if v == nil: return nil
95 dup(unescapeTag($v))
96
97proc frq_irc_escape_tag_value*(v: cstring): cstring {.exportc, dynlib.} =
98 if v == nil: return dup("")
99 dup(escapeTagValue($v))
100
101proc frq_irc_nick_of*(prefix: cstring): cstring {.exportc, dynlib.} =
102 if prefix == nil: return nil
103 dup(nickOf($prefix))
Nim owns the screen, Dart owns the pixels 43a02c2 nandi 14h ago104
Nim under the existing UI, not instead of it 56551a8 nandi 14h ago105# --------------------------------------------------------------- transport
106#
107# `frq.net`'s three operations, for `frq.net.nim` to install. This is the
108# wiring that matters: the existing ClojureDart screens, cells and actions are
109# untouched, and only the socket underneath them becomes Nim.
110#
111# Polled rather than callback-driven, for the reason the UI is: a Dart callback
112# invoked from a foreign thread has to be marshalled onto the main isolate, and
113# a timer on the Dart side does the same job with no mechanism at all.
114
115proc frq_trace*(topic, msg: cstring) {.exportc, dynlib.} =
116 ## Let the Dart side log through the same facility, so one FRQ_TRACE=1 gives
117 ## one interleaved story instead of two half-ones in different places.
118 if topic != nil and msg != nil:
119 trace($topic, $msg)
120
121proc frq_conn_open*(host: cstring, port: cint, tls: cint) {.exportc, dynlib.} =
122 if host == nil: return
123 tr.open(tr.ConnConfig(host: $host, port: port.int, tls: tls != 0))
124
125proc frq_conn_send*(line: cstring) {.exportc, dynlib.} =
126 if line != nil: tr.send($line)
127
128proc frq_conn_close*() {.exportc, dynlib.} =
129 tr.close()
130
131proc frq_conn_recv*(): cstring {.exportc, dynlib.} =
132 ## The next line, or null when there is none waiting. Never blocks.
133 let (ok, line) = tr.tryLine()
134 if ok: dup(line) else: nil
135
136proc frq_conn_event*(): cstring {.exportc, dynlib.} =
137 ## The next transport event — "open", "close: …", "error: …" — or null.
138 let (ok, e) = tr.tryEvent()
139 if ok: dup(e) else: nil