Differences From
Artifact [c52f4282f5]:
113 113 ct.exns.tx(msg, self.src.file, self.line or 0, ...):throw()
114 114 end;
115 115 insert = function(self, block)
116 116 block.origin = self:clone()
117 117 table.insert(self.sec.blocks,block)
118 118 return block
119 119 end;
120 + init = function(ctx, doc, src)
121 + ctx.line = 0
122 + ctx.doc = doc
123 + ctx.doc.src = src
124 + ctx.sec = doc:mksec() -- toplevel section
125 + ctx.sec.origin = ctx:clone()
126 + end;
120 127 ref = function(self,id)
121 128 if not id:find'%.' then
122 129 local rid = self.sec.refs[id]
123 130 if self.sec.refs[id] then
124 131 return self.sec.refs[id], id, self.sec
125 132 else self:fail("no such ref %s in current section", id or '') end
126 133 else
................................................................................
184 191 end
185 192 return ct.ext.loaded[name].default
186 193 end;
187 194 context_var = function(self, var, ctx, test)
188 195 local fail = function(...)
189 196 if test then return false end
190 197 ctx:fail(...)
198 + end
199 + local scanParents = function(k)
200 + for k,p in pairs(self.parents) do
201 + local v = p:context_var(k, ctx, true)
202 + if v ~= false then return v end
203 + end
191 204 end
192 205 if startswith(var, 'cortav.') then
193 206 local v = var:sub(8)
194 207 if v == 'page' then
195 208 if ctx.page then return tostring(ctx.page)
196 209 else return '(unpaged)' end
197 210 elseif v == 'renderer' then
................................................................................
217 230 local val = os.getenv(v)
218 231 if not val then
219 232 return fail('undefined environment variable %s', v)
220 233 end
221 234 elseif self.stage.kind == 'render' and startswith(var, self.stage.format..'.') then
222 235 -- TODO query the renderer somehow
223 236 return fail('renderer %s does not implement variable %s', self.stage.format, var)
237 + elseif startswith(var, 'super.') then
238 + local sp = scanParents(var:sub(8))
239 + if sp == nil then
240 + if test then return false else return '' end
241 + else
242 + return sp
243 + end
224 244 elseif self.vars[var] then
225 245 return self.vars[var]
226 246 else
247 + local sp = scanParents(var)
248 + if sp then return sp end
227 249 if test then return false end
228 250 return '' -- is this desirable behavior?
229 251 end
230 252 end;
231 253 job = function(self, name, pred, ...) -- convenience func
232 254 return self.docjob:fork(name, pred, ...)
233 - end
255 + end;
256 + sub = function(self, ctx)
257 + -- convenience function for single-inheritance structure
258 + -- sets up a doc/ctx pair for a subdocument embedded in the source
259 + -- of a gretaer document, pointing subdoc props to global tables/values
260 + local newdoc = ct.doc.mk(self)
261 + newdoc.meta = self.meta
262 + newdoc.ext = self.ext
263 + newdoc.enc = self.enc
264 + newdoc.stage = self.stage
265 + -- vars are handled through proper recursion across all parents and
266 + -- are intentionally excluded here; subdocs can have their own vars
267 + -- without losing access to parent vars
268 + local nctx = ctx:clone()
269 + nctx:init(newdoc, ctx.src)
270 + nctx.line = ctx.line
271 + return newdoc, nctx
272 + end;
234 273 };
235 - mk = function() return {
274 + mk = function(...) return {
236 275 sections = {};
237 276 secorder = {};
238 277 embed = {};
239 278 meta = {};
240 279 vars = {};
280 + parents = {...};
241 281 ext = {
242 282 inhibit = {};
243 283 need = {};
244 284 use = {};
245 285 };
246 286 enc = ss.str.enc.utf8;
247 287 } end;
................................................................................
594 634 cmd = cmd;
595 635 args = args;
596 636 crit = crit;
597 637 failthru = failthru;
598 638 spans = spans;
599 639 }
600 640 end
641 + end
642 + local function rawcode(s, c) -- raw
643 + local o = c:clone();
644 + local str = ''
645 + for c, p in ss.str.each(c.doc.enc, s) do
646 + local q = p:esc()
647 + if q then
648 + str = str .. q
649 + p.next.byte = p.next.byte + #q
650 + else
651 + str = str .. c
652 + end
653 + end
654 + return {
655 + kind = 'format';
656 + style = 'literal';
657 + spans = {{
658 + kind = 'raw';
659 + spans = {str};
660 + origin = o;
661 + }};
662 + origin = o;
663 + }
601 664 end
602 665 ct.spanctls = {
603 666 {seq = '!', parse = formatter 'emph'};
604 667 {seq = '*', parse = formatter 'strong'};
605 668 {seq = '~', parse = formatter 'strike'};
606 669 {seq = '+', parse = formatter 'insert'};
670 + {seq = '`\\', parse = rawcode};
671 + {seq = '\\\\', parse = rawcode};
607 672 {seq = '\\', parse = function(s, c) -- raw
608 673 return {
609 674 kind = 'raw';
610 675 spans = {s};
611 676 origin = c:clone();
612 677 }
613 - end};
614 - {seq = '`\\', parse = function(s, c) -- raw
615 - local o = c:clone();
616 - local str = ''
617 - for c, p in ss.str.each(c.doc.enc, s) do
618 - local q = p:esc()
619 - if q then
620 - str = str .. q
621 - p.next.byte = p.next.byte + #q
622 - else
623 - str = str .. c
624 - end
625 - end
626 - return {
627 - kind = 'format';
628 - style = 'literal';
629 - spans = {{
630 - kind = 'raw';
631 - spans = {str};
632 - origin = o;
633 - }};
634 - origin = o;
635 - }
636 678 end};
637 679 {seq = '`', parse = formatter 'literal'};
638 680 {seq = '$', parse = formatter 'variable'};
639 681 {seq = '^', parse = function(s,c) --footnotes
640 682 local r, t = s:match '^([^%s]+)%s*(.-)$'
641 683 return {
642 684 kind = 'footnote';
................................................................................
984 1026 local sp = ct.parse_span(txt, c)
985 1027 c.doc.docjob:hook('meddle_span', sp, last)
986 1028 table.insert(last.lines, sp)
987 1029 j:hook('block_aside_attach', c, last, sp, l)
988 1030 j:hook('block_aside_line_insert', c, last, sp, l)
989 1031 end
990 1032 end};
991 - {pred = function(s,c) return s:match'^[*:]' end, fn = blockwrap(function(l,c) -- list
1033 + {pred = function(s,c) return s:match'^[*:]' end,
1034 + fn = blockwrap(function(l,c) -- list
992 1035 local stars = l:match '^([*:]+)'
993 1036 local depth = utf8.len(stars)
994 1037 local id, txt = l:sub(#stars+1):match '^(.-)%s*(.-)$'
995 1038 local ordered = stars:sub(#stars) == ':'
996 1039 if id == '' then id = nil end
997 1040 return {
998 1041 kind = 'list-item';
................................................................................
1073 1116 c:fail('extension %s does not support critical directive %s', cmd, topcmd)
1074 1117 end
1075 1118 end
1076 1119 elseif crit == '!' then
1077 1120 c:fail('critical directive %s not supported',cmd)
1078 1121 end
1079 1122 end;};
1123 + {pred = function(s) return s:match '^(>+)([^%s]*)%s*(.*)$' end,
1124 + fn = function(l,c,j,d)
1125 + local lvl,id,txt = l:match '^(>+)([^%s]*)%s*(.*)$'
1126 + lvl = utf8.len(lvl)
1127 + local last = d[#d]
1128 + local node
1129 + local ctx
1130 + if last and last.kind == 'quote' and (id == nil or id == '' or id == last.id) then
1131 + node = last
1132 + ctx = node.ctx
1133 + ctx.line = c.line -- is this enough??
1134 + else
1135 + local doc
1136 + doc, ctx = c.doc:sub(c)
1137 + node = { kind = 'quote', doc = doc, ctx = ctx, id = id }
1138 + j:hook('block_insert', c, node, l)
1139 + table.insert(d, node)
1140 + end
1141 +
1142 + ct.parse_line(txt, ctx, ctx.sec.blocks)
1143 + end};
1080 1144 {seq = '~~~', fn = blockwrap(function(l,c,j)
1081 1145 local extract = function(ptn, str)
1082 1146 local start, stop = str:find(ptn)
1083 1147 if not start then return nil, str end
1084 1148 local ex = str:sub(start,stop)
1085 1149 local n = str:sub(1,start-1) .. str:sub(stop+1)
1086 1150 return ex, n
................................................................................
1167 1231 if ctx.mode.expand
1168 1232 then newline = ct.parse_span(l, ctx)
1169 1233 else newline = {l}
1170 1234 end
1171 1235 table.insert(ctx.mode.listing.lines, newline)
1172 1236 job:hook('block_listing_newline',ctx,ctx.mode.listing,newline)
1173 1237 end
1174 - else
1238 + elseif ctx.mode.kind == 'quote' then
1239 + else
1175 1240 local mf = job:proc('modes', ctx.mode.kind)
1176 1241 if not mf then
1177 1242 ctx:fail('unimplemented syntax mode %s', ctx.mode.kind)
1178 1243 end
1179 1244 mf(job, ctx, l, dest) --NOTE: you are responsible for triggering the appropriate hooks if you insert anything!
1180 1245 end
1181 1246 else
................................................................................
1190 1255 end
1191 1256 return false
1192 1257 end
1193 1258
1194 1259 if not tryseqs(ct.ctlseqs) then
1195 1260 local found = false
1196 1261
1197 - for eb, ext, state in job:each('blocks') do
1262 + for eb, ext, state in job:each 'blocks' do
1198 1263 if tryseqs(eb, state) then found = true break end
1199 1264 end
1200 1265
1201 1266 if not found then
1202 1267 ctx:fail 'incomprehensible input line'
1203 1268 end
1204 1269 end
................................................................................
1214 1279 end
1215 1280
1216 1281 function ct.parse(file, src, mode, setup)
1217 1282 -- this object is threaded down through the parse tree
1218 1283 -- and copied to store information like the origin of the
1219 1284 -- element in the source code
1220 1285 local ctx = ct.ctx.mk(src)
1221 - ctx.line = 0
1222 - ctx.doc = ct.doc.mk()
1223 - ctx.doc.src = src
1224 - ctx.sec = ctx.doc:mksec() -- toplevel section
1225 - ctx.sec.origin = ctx:clone()
1286 + ctx:init(ct.doc.mk(), src)
1226 1287 ctx.lang = mode['meta:lang']
1227 1288 if mode['parse:enc'] then
1228 1289 local e = ss.str.enc[mode['parse:enc']]
1229 1290 if not e then
1230 1291 ct.exns.enc('requested encoding not supported',mode['parse:enc']):throw()
1231 1292 end
1232 1293 ctx.doc.enc = e