nandi/frqpublic Fork 0
43a02c2ddd7ebb7cdcd7d46f8c9a20b5b1b55b5e
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 · 131 lines · 5.2 KBNim Blame HistoryRaw
One frontend where there were three, and a core that is not Clojure 438b247 nandi yesterday1## 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.
22
23import std/json
Nim owns the screen, Dart owns the pixels 43a02c2 nandi yesterday24import frq/[ircparse, ui, state]
25import frq/screens/connect as connectScreen
One frontend where there were three, and a core that is not Clojure 438b247 nandi yesterday26
27proc NimMain() {.importc.}
28
29var initialised = false
30
31proc frq_init*() {.exportc, dynlib.} =
32 ## Set Nim's runtime up. Idempotent, because a binding that guesses wrong
33 ## about whether it has been called should be harmless rather than fatal.
34 if not initialised:
35 NimMain()
36 initialised = true
37
38proc dup(s: string): cstring =
39 ## A copy of `s` that outlives this call, for the caller to `frq_free`.
40 ## `allocShared0` and not `alloc0`: the Dart side may free it from a
41 ## different thread than the one that made it.
42 let n = s.len
43 let p = cast[cstring](allocShared0(n + 1))
44 if n > 0:
45 copyMem(p, unsafeAddr s[0], n)
46 p
47
48proc frq_free*(p: cstring) {.exportc, dynlib.} =
49 ## Free what one of the functions below returned. Null is fine.
50 if p != nil:
51 deallocShared(p)
52
53proc frq_version*(): cstring {.exportc, dynlib.} =
54 ## Static storage, deliberately: this one is NOT freed, and is the only
55 ## exception to the rule above. It exists so a binding can check at load
56 ## time that the library it found is the one it was built against.
57 "0.1.0"
58
59# ----------------------------------------------------------------- irc/parse
60
61proc frq_irc_parse_line*(line: cstring): cstring {.exportc, dynlib.} =
62 ## An IRC line as JSON: `{raw, tags, account, prefix, command, params}`.
63 ##
64 ## `tags`, `account` and `prefix` are JSON null where the line carried none,
65 ## which is the distinction `frq.irc.parse` draws with nil and every caller
66 ## of it depends on — a PRIVMSG from a server with no prefix is not the same
67 ## line as one from a nick.
68 if line == nil: return dup("null")
69 let p = parseLine($line)
70 var o = newJObject()
71 o["raw"] = %p.raw
72 o["tags"] = if p.hasTags: %p.tags else: newJNull()
73 o["account"] = if p.hasAccount: %p.account else: newJNull()
74 o["prefix"] = if p.hasPrefix: %p.prefix else: newJNull()
75 o["command"] = %p.command
76 o["params"] = %p.params
77 dup($o)
78
79proc frq_irc_tag_value*(tags, key: cstring): cstring {.exportc, dynlib.} =
80 ## One tag's value, unescaped — or **null** where the tag is absent or
81 ## empty, which IRCv3 says are the same thing. Null and not "" on purpose:
82 ## see `tagValue`.
83 if tags == nil or key == nil: return nil
84 let (v, ok) = tagValue($tags, $key)
85 if ok: dup(v) else: nil
86
87proc frq_irc_unescape_tag*(v: cstring): cstring {.exportc, dynlib.} =
88 if v == nil: return nil
89 dup(unescapeTag($v))
90
91proc frq_irc_escape_tag_value*(v: cstring): cstring {.exportc, dynlib.} =
92 if v == nil: return dup("")
93 dup(escapeTagValue($v))
94
95proc frq_irc_nick_of*(prefix: cstring): cstring {.exportc, dynlib.} =
96 if prefix == nil: return nil
97 dup(nickOf($prefix))
Nim owns the screen, Dart owns the pixels 43a02c2 nandi yesterday98
99# ------------------------------------------------------------------- the UI
100#
101# The spike's real claim: Nim owns the state and the screen, Dart owns the
102# pixels, and the only things crossing are a tree going out and an event id
103# coming back. See `frq/ui.nim`.
104
105proc frq_ui_render*(): cstring {.exportc, dynlib.} =
106 ## The current screen as a widget tree, in JSON. Pure: calling it twice with
107 ## no dispatch between gives the same answer, which is what lets the
108 ## renderer rebuild whenever Flutter asks rather than when Nim says so.
109 dup($connectScreen.connectScreen(app).toJson)
110
111proc frq_ui_dispatch*(event: cstring): cstring {.exportc, dynlib.} =
112 ## Apply an event and answer with the tree it produced.
113 ##
114 ## One call rather than dispatch-then-render, and not to save a crossing:
115 ## it makes the pair atomic. Two calls leave a window in which Dart could
116 ## render a state nothing asked for, which is the sort of thing that shows
117 ## up once a week and never in a test.
118 ##
119 ## A malformed event is ignored rather than fatal — it arrives from a tree
120 ## the renderer may have been holding for a frame, which is a normal race.
121 if event != nil:
122 try:
123 dispatch(parseJson($event))
124 except JsonParsingError:
125 discard
126 dup($connectScreen.connectScreen(app).toJson)
127
128proc frq_ui_reset*() {.exportc, dynlib.} =
129 ## Back to a fresh state. For tests, and for a renderer that wants a known
130 ## starting point rather than whatever the last run left.
131 app = initState()