mirror of
https://github.com/logos-messaging/nim-ffi.git
synced 2026-07-27 18:13:12 +00:00
97 lines
3.1 KiB
Nim
97 lines
3.1 KiB
Nim
## Unicode-aware identifier casing and doc-comment rendering, shared by codegen
|
|
## and the FFI macro.
|
|
|
|
import std/[strutils, unicode]
|
|
|
|
func docLines(doc: string): seq[string] =
|
|
## `doc` split into lines, trailing blank ones dropped.
|
|
if doc.strip().len == 0:
|
|
return @[]
|
|
var lines = doc.splitLines()
|
|
while lines.len > 0 and lines[^1].strip().len == 0:
|
|
lines.setLen(lines.len - 1)
|
|
return lines
|
|
|
|
func renderDocComment*(doc, indent, prefix: string): seq[string] =
|
|
## `doc` as one `prefix`-led line comment per source line, at `indent`.
|
|
var rendered: seq[string] = @[]
|
|
for line in docLines(doc):
|
|
# A trailing `\` would splice the next generated line into a `//` comment.
|
|
rendered.add(
|
|
indent & (prefix & line).strip(leading = false, chars = Whitespace + {'\\'})
|
|
)
|
|
return rendered
|
|
|
|
func renderMemberDocComment*(doc: string): seq[string] =
|
|
## `///` at the indent C++ class members and Rust `impl` items sit at.
|
|
return doc.renderDocComment(" ", "/// ")
|
|
|
|
func escapeBlockComment(line: string): string =
|
|
## `*/` would close the comment early and splice the rest in as code.
|
|
return line.replace("*/", "* /")
|
|
|
|
func renderBlockDocComment*(doc: string, indent = ""): seq[string] =
|
|
## `doc` as a `/** ... */` block at `indent`; one-liners stay on one line.
|
|
let lines = docLines(doc)
|
|
if lines.len == 0:
|
|
return @[]
|
|
if lines.len == 1:
|
|
return @[indent & "/** " & escapeBlockComment(lines[0].strip()) & " */"]
|
|
var rendered = @[indent & "/**"]
|
|
for line in lines:
|
|
rendered.add((indent & " * " & escapeBlockComment(line)).strip(leading = false))
|
|
rendered.add(indent & " */")
|
|
return rendered
|
|
|
|
proc toLower*(s: string): string =
|
|
## Unicode-aware lowercase for an entire string.
|
|
var buf = ""
|
|
for r in runes(s):
|
|
buf.add($r.toLower())
|
|
return buf
|
|
|
|
proc camelToSnakeCase*(s: string): string =
|
|
## camelCase → snake_case, e.g. "delayMs" → "delay_ms".
|
|
var snake = ""
|
|
var first = true
|
|
for r in runes(s):
|
|
if r.isUpper() and not first:
|
|
snake.add('_')
|
|
snake.add($r.toLower())
|
|
first = false
|
|
return snake
|
|
|
|
func capitalizeFirstLetter*(s: string): string =
|
|
## Returns `s` with its first rune uppercased, rest unchanged.
|
|
if s.len == 0:
|
|
return s
|
|
var runesSeq = toRunes(s)
|
|
runesSeq[0] = runesSeq[0].toUpper()
|
|
return $runesSeq
|
|
|
|
func identToUpperSnake*(s: string): string =
|
|
## Nim identifier → UPPER_SNAKE, keeping acronym runs intact: "maxPeers" and
|
|
## "MAX_PEERS" both give "MAX_PEERS", "httpTTL" gives "HTTP_TTL".
|
|
var upper = ""
|
|
let rs = toRunes(s)
|
|
for i, r in rs:
|
|
if r == Rune('_'):
|
|
if upper.len > 0 and upper[^1] != '_':
|
|
upper.add('_')
|
|
continue
|
|
let startsWord =
|
|
i > 0 and r.isUpper() and
|
|
(not rs[i - 1].isUpper() or (i + 1 < rs.len and rs[i + 1].isLower()))
|
|
if startsWord and upper.len > 0 and upper[^1] != '_':
|
|
upper.add('_')
|
|
upper.add($r.toUpper())
|
|
return upper
|
|
|
|
proc snakeToPascalCase*(s: string): string =
|
|
## snake_case → PascalCase, e.g. "hello_world" → "HelloWorld".
|
|
let parts = s.split('_')
|
|
var pascal = ""
|
|
for p in parts:
|
|
pascal.add capitalizeFirstLetter(p)
|
|
return pascal
|