code wiki / _hdl_build / nx_md_lib.nx
nx_md_lib.nx source
↩ module page · 146 lines · 10217 B
1// nx_md_lib.nx -- the Markdown -> HTML renderer as a LIBRARY (rule 15 DRY): shared by the nx_md CLI organ
2// (Linux/dispatch path) and the NishiOS app layer (nx_nishios_docview), where there is no process fork yet so
3// renderers LINK instead of exec. Pure functions, no main. license_tier: ORIGINAL
4import "nx_syscalls.nx"
5
6func md_cat(dst: *u8, off: i64, s: *u8) -> i64 { var i: i64=0; while s[i]!=(0 as u8) { dst[off+i]=s[i]; i=i+1 } return off+i }
7
8// emit one char HTML-escaped (& < >) into dst; return new offset
9func md_esc1(dst: *u8, o: i64, c: i64) -> i64 {
10 if c==38 { return md_cat(dst,o,"&" as *u8) }
11 if c==60 { return md_cat(dst,o,"<" as *u8) }
12 if c==62 { return md_cat(dst,o,">" as *u8) }
13 dst[o]=c as u8; return o+1
14}
15
16// inline span: **bold** *italic* `code` [text](url); escape the rest. src[s,e) -> dst.
17func md_inline(dst: *u8, off: i64, src: *u8, s: i64, e: i64) -> i64 {
18 var o: i64=off; var i: i64=s
19 var bold: i64=0; var ital: i64=0; var code: i64=0
20 while i<e {
21 let c: i64=src[i] as i64
22 var handled: i64=0
23 // inline code toggles first (inside code, no other markup)
24 if handled==0 { if c==96 { if code==0 { o=md_cat(dst,o,"<code>" as *u8); code=1 } else { o=md_cat(dst,o,"</code>" as *u8); code=0 } i=i+1; handled=1 } }
25 if handled==0 { if code==1 { o=md_esc1(dst,o,c); i=i+1; handled=1 } }
26 // **bold**
27 if handled==0 { if c==42 { if i+1<e { if src[i+1]==(42 as u8) {
28 if bold==0 { o=md_cat(dst,o,"<b>" as *u8); bold=1 } else { o=md_cat(dst,o,"</b>" as *u8); bold=0 }
29 i=i+2; handled=1
30 } } } }
31 // *italic*
32 if handled==0 { if c==42 { if ital==0 { o=md_cat(dst,o,"<i>" as *u8); ital=1 } else { o=md_cat(dst,o,"</i>" as *u8); ital=0 } i=i+1; handled=1 } }
33 // [text](url)
34 if handled==0 { if c==91 {
35 var t: i64=i+1; var te: i64=0-1
36 while t<e { if src[t]==(93 as u8) { te=t; t=e } else { t=t+1 } }
37 var ok: i64=0; var ue: i64=0-1; var us: i64=0
38 if te>=0 { if te+1<e { if src[te+1]==(40 as u8) {
39 us=te+2; var u: i64=us
40 while u<e { if src[u]==(41 as u8) { ue=u; u=e } else { u=u+1 } }
41 if ue>=0 { ok=1 }
42 } } }
43 if ok==1 {
44 o=md_cat(dst,o,"<a href=\"" as *u8)
45 var k: i64=us; while k<ue { o=md_esc1(dst,o,src[k] as i64); k=k+1 }
46 o=md_cat(dst,o,"\">" as *u8)
47 k=i+1; while k<te { o=md_esc1(dst,o,src[k] as i64); k=k+1 }
48 o=md_cat(dst,o,"</a>" as *u8)
49 i=ue+1; handled=1
50 }
51 } }
52 if handled==0 { o=md_esc1(dst,o,c); i=i+1 }
53 }
54 if code==1 { o=md_cat(dst,o,"</code>" as *u8) }
55 if ital==1 { o=md_cat(dst,o,"</i>" as *u8) }
56 if bold==1 { o=md_cat(dst,o,"</b>" as *u8) }
57 return o
58}
59
60// is src[s,e) all '-' / '*' / '_' (a horizontal rule), min 3
61func md_all_dash(src: *u8, s: i64, e: i64) -> i64 {
62 if e-s<3 { return 0 }
63 var i: i64=s; var ok: i64=1
64 while i<e { let c: i64=src[i] as i64; if c!=45 { if c!=42 { if c!=95 { ok=0 } } } i=i+1 }
65 return ok
66}
67
68// small int emit into a buffer (for <hN>)
69func md_num_to(dst: *u8, off: i64, v: i64) -> i64 { dst[off]=(48+v) as u8; return off+1 }
70
71func md_to_html(src: *u8, slen: i64, out: *u8, dlurl: *u8) -> i64 {
72 var o: i64=0
73 o=md_cat(out,o,"<!DOCTYPE html>\n<html lang=en><head><meta charset=utf-8><meta name=viewport content=\"width=device-width,initial-scale=1\"><title>Document Preview - Nishi Office</title><style>\nbody{font:16px/1.65 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;margin:0;background:#eef1f5;color:#1a1d21}\nheader{background:#0f172a;color:#fff;padding:16px 24px}\nheader h1{margin:0;font-size:17px;font-weight:600}\n.doc{max-width:820px;margin:22px auto;background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:34px 46px;box-shadow:0 1px 4px rgba(15,23,42,.08)}\n.doc h1{font-size:26px;margin:18px 0 10px}.doc h2{font-size:21px;margin:20px 0 8px;border-bottom:1px solid #eef2f7;padding-bottom:5px}.doc h3{font-size:18px;margin:16px 0 6px}\n.doc p{margin:10px 0}.doc ul,.doc ol{margin:10px 0 10px 8px;padding-left:22px}.doc li{margin:4px 0}\n.doc code{background:#f1f5f9;border-radius:4px;padding:1px 5px;font-family:ui-monospace,Consolas,monospace;font-size:14px}\n.doc pre{background:#0f172a;color:#e2e8f0;border-radius:8px;padding:14px 16px;overflow-x:auto}.doc pre code{background:none;color:inherit;padding:0}\n.doc blockquote{border-left:4px solid #cbd5e1;margin:12px 0;padding:4px 16px;color:#475569}\n.doc hr{border:none;border-top:1px solid #e2e8f0;margin:20px 0}\n.doc a{color:#2563eb}.doc table{border-collapse:collapse;margin:14px 0}.doc td,.doc th{border:1px solid #cbd5e1;padding:6px 11px}\n.bar{max-width:820px;margin:0 auto 6px;text-align:right}.dl{display:inline-block;padding:9px 18px;background:#2563eb;color:#fff;border-radius:8px;text-decoration:none;font-size:14px;font-weight:500}\nfooter{max-width:820px;margin:14px auto 46px;color:#64748b;font-size:13px;text-align:center}\n</style></head>\n<body>\n<header><h1>📄 Document Preview — Nishi Office</h1></header>\n<div class=doc>\n" as *u8)
74 var i: i64=0
75 var infence: i64=0
76 var inlist: i64=0 // 0 none, 1 ul, 2 ol
77 while i<slen {
78 var e: i64=i
79 while e<slen { if src[e]==(10 as u8) { break } e=e+1 }
80 var end: i64=e
81 if end>i { if src[end-1]==(13 as u8) { end=end-1 } }
82 // fenced code
83 var isfence: i64=0
84 if end>=i+3 { if src[i]==(96 as u8) { if src[i+1]==(96 as u8) { if src[i+2]==(96 as u8) { isfence=1 } } } }
85 if isfence==1 {
86 if infence==0 { if inlist!=0 { if inlist==1 { o=md_cat(out,o,"</ul>\n" as *u8) } else { o=md_cat(out,o,"</ol>\n" as *u8) } inlist=0 } o=md_cat(out,o,"<pre><code>" as *u8); infence=1 } else { o=md_cat(out,o,"</code></pre>\n" as *u8); infence=0 }
87 i=e+1
88 } else {
89 if infence==1 {
90 var k: i64=i; while k<end { o=md_esc1(out,o,src[k] as i64); k=k+1 } o=md_cat(out,o,"\n" as *u8)
91 i=e+1
92 } else {
93 // count leading '#'
94 var hn: i64=0; var hh: i64=0
95 while hh<end-i { if src[i+hh]==(35 as u8) { hn=hn+1; hh=hh+1 } else { hh=end-i } }
96 var isheading: i64=0
97 if hn>=1 { if hn<=6 { if i+hn<end { if src[i+hn]==(32 as u8) { isheading=1 } } } }
98 // list markers
99 var isul: i64=0
100 if end>=i+2 { if src[i+1]==(32 as u8) { if src[i]==(45 as u8) { isul=1 } else { if src[i]==(42 as u8) { isul=1 } else { if src[i]==(43 as u8) { isul=1 } } } } }
101 var isol: i64=0
102 if end>=i+3 { var d: i64=i; var nd: i64=0; while d<end { let dc: i64=src[d] as i64; if dc>=48 { if dc<=57 { nd=nd+1; d=d+1 } else { d=end } } else { d=end } }
103 if nd>=1 { if i+nd<end { if src[i+nd]==(46 as u8) { if i+nd+1<end { if src[i+nd+1]==(32 as u8) { isol=1 } } } } } }
104 var isquote: i64=0
105 if end>i { if src[i]==(62 as u8) { isquote=1 } }
106 var ishr: i64=md_all_dash(src, i, end)
107 if end<=i {
108 // blank line: close any open list
109 if inlist!=0 { if inlist==1 { o=md_cat(out,o,"</ul>\n" as *u8) } else { o=md_cat(out,o,"</ol>\n" as *u8) } inlist=0 }
110 } else {
111 if isheading==1 {
112 if inlist!=0 { if inlist==1 { o=md_cat(out,o,"</ul>\n" as *u8) } else { o=md_cat(out,o,"</ol>\n" as *u8) } inlist=0 }
113 o=md_cat(out,o,"<h" as *u8); o=md_num_to(out,o,hn); o=md_cat(out,o,">" as *u8)
114 o=md_inline(out,o,src,i+hn+1,end)
115 o=md_cat(out,o,"</h" as *u8); o=md_num_to(out,o,hn); o=md_cat(out,o,">\n" as *u8)
116 } else { if ishr==1 {
117 if inlist!=0 { if inlist==1 { o=md_cat(out,o,"</ul>\n" as *u8) } else { o=md_cat(out,o,"</ol>\n" as *u8) } inlist=0 }
118 o=md_cat(out,o,"<hr>\n" as *u8)
119 } else { if isul==1 {
120 if inlist!=1 { if inlist==2 { o=md_cat(out,o,"</ol>\n" as *u8) } o=md_cat(out,o,"<ul>\n" as *u8); inlist=1 }
121 o=md_cat(out,o,"<li>" as *u8); o=md_inline(out,o,src,i+2,end); o=md_cat(out,o,"</li>\n" as *u8)
122 } else { if isol==1 {
123 if inlist!=2 { if inlist==1 { o=md_cat(out,o,"</ul>\n" as *u8) } o=md_cat(out,o,"<ol>\n" as *u8); inlist=2 }
124 var ms: i64=i; while ms<end { if src[ms]==(32 as u8) { ms=ms+1; break } ms=ms+1 }
125 o=md_cat(out,o,"<li>" as *u8); o=md_inline(out,o,src,ms,end); o=md_cat(out,o,"</li>\n" as *u8)
126 } else { if isquote==1 {
127 if inlist!=0 { if inlist==1 { o=md_cat(out,o,"</ul>\n" as *u8) } else { o=md_cat(out,o,"</ol>\n" as *u8) } inlist=0 }
128 var qs: i64=i+1; if qs<end { if src[qs]==(32 as u8) { qs=qs+1 } }
129 o=md_cat(out,o,"<blockquote>" as *u8); o=md_inline(out,o,src,qs,end); o=md_cat(out,o,"</blockquote>\n" as *u8)
130 } else {
131 if inlist!=0 { if inlist==1 { o=md_cat(out,o,"</ul>\n" as *u8) } else { o=md_cat(out,o,"</ol>\n" as *u8) } inlist=0 }
132 o=md_cat(out,o,"<p>" as *u8); o=md_inline(out,o,src,i,end); o=md_cat(out,o,"</p>\n" as *u8)
133 } } } } }
134 }
135 i=e+1
136 }
137 }
138 }
139 if infence==1 { o=md_cat(out,o,"</code></pre>\n" as *u8) }
140 if inlist==1 { o=md_cat(out,o,"</ul>\n" as *u8) }
141 if inlist==2 { o=md_cat(out,o,"</ol>\n" as *u8) }
142 o=md_cat(out,o,"</div>\n" as *u8)
143 if (dlurl as i64)!=0 { o=md_cat(out,o,"<div class=bar><a class=dl href=\"" as *u8); o=md_cat(out,o,dlurl); o=md_cat(out,o,"\">⬇ Download source</a></div>\n" as *u8) }
144 o=md_cat(out,o,"<footer>Rendered natively from Markdown by <b>nx_md</b> · viewable inline in the Nishi browser</footer>\n</body></html>\n" as *u8)
145 return o
146}