@@ -116,8 +116,15 @@ block.origin = self:clone() table.insert(self.sec.blocks,block) return block end; + init = function(ctx, doc, src) + ctx.line = 0 + ctx.doc = doc + ctx.doc.src = src + ctx.sec = doc:mksec() -- toplevel section + ctx.sec.origin = ctx:clone() + end; ref = function(self,id) if not id:find'%.' then local rid = self.sec.refs[id] if self.sec.refs[id] then @@ -187,8 +194,14 @@ context_var = function(self, var, ctx, test) local fail = function(...) if test then return false end ctx:fail(...) + end + local scanParents = function(k) + for k,p in pairs(self.parents) do + local v = p:context_var(k, ctx, true) + if v ~= false then return v end + end end if startswith(var, 'cortav.') then local v = var:sub(8) if v == 'page' then @@ -220,25 +233,52 @@ end elseif self.stage.kind == 'render' and startswith(var, self.stage.format..'.') then -- TODO query the renderer somehow return fail('renderer %s does not implement variable %s', self.stage.format, var) + elseif startswith(var, 'super.') then + local sp = scanParents(var:sub(8)) + if sp == nil then + if test then return false else return '' end + else + return sp + end elseif self.vars[var] then return self.vars[var] else + local sp = scanParents(var) + if sp then return sp end if test then return false end return '' -- is this desirable behavior? end end; job = function(self, name, pred, ...) -- convenience func return self.docjob:fork(name, pred, ...) - end + end; + sub = function(self, ctx) + -- convenience function for single-inheritance structure + -- sets up a doc/ctx pair for a subdocument embedded in the source + -- of a gretaer document, pointing subdoc props to global tables/values + local newdoc = ct.doc.mk(self) + newdoc.meta = self.meta + newdoc.ext = self.ext + newdoc.enc = self.enc + newdoc.stage = self.stage + -- vars are handled through proper recursion across all parents and + -- are intentionally excluded here; subdocs can have their own vars + -- without losing access to parent vars + local nctx = ctx:clone() + nctx:init(newdoc, ctx.src) + nctx.line = ctx.line + return newdoc, nctx + end; }; - mk = function() return { + mk = function(...) return { sections = {}; secorder = {}; embed = {}; meta = {}; vars = {}; + parents = {...}; ext = { inhibit = {}; need = {}; use = {}; @@ -597,43 +637,45 @@ failthru = failthru; spans = spans; } end + end + local function rawcode(s, c) -- raw + local o = c:clone(); + local str = '' + for c, p in ss.str.each(c.doc.enc, s) do + local q = p:esc() + if q then + str = str .. q + p.next.byte = p.next.byte + #q + else + str = str .. c + end + end + return { + kind = 'format'; + style = 'literal'; + spans = {{ + kind = 'raw'; + spans = {str}; + origin = o; + }}; + origin = o; + } end ct.spanctls = { {seq = '!', parse = formatter 'emph'}; {seq = '*', parse = formatter 'strong'}; {seq = '~', parse = formatter 'strike'}; {seq = '+', parse = formatter 'insert'}; + {seq = '`\\', parse = rawcode}; + {seq = '\\\\', parse = rawcode}; {seq = '\\', parse = function(s, c) -- raw return { kind = 'raw'; spans = {s}; origin = c:clone(); } - end}; - {seq = '`\\', parse = function(s, c) -- raw - local o = c:clone(); - local str = '' - for c, p in ss.str.each(c.doc.enc, s) do - local q = p:esc() - if q then - str = str .. q - p.next.byte = p.next.byte + #q - else - str = str .. c - end - end - return { - kind = 'format'; - style = 'literal'; - spans = {{ - kind = 'raw'; - spans = {str}; - origin = o; - }}; - origin = o; - } end}; {seq = '`', parse = formatter 'literal'}; {seq = '$', parse = formatter 'variable'}; {seq = '^', parse = function(s,c) --footnotes @@ -987,9 +1029,10 @@ j:hook('block_aside_attach', c, last, sp, l) j:hook('block_aside_line_insert', c, last, sp, l) end end}; - {pred = function(s,c) return s:match'^[*:]' end, fn = blockwrap(function(l,c) -- list + {pred = function(s,c) return s:match'^[*:]' end, + fn = blockwrap(function(l,c) -- list local stars = l:match '^([*:]+)' local depth = utf8.len(stars) local id, txt = l:sub(#stars+1):match '^(.-)%s*(.-)$' local ordered = stars:sub(#stars) == ':' @@ -1076,8 +1119,29 @@ elseif crit == '!' then c:fail('critical directive %s not supported',cmd) end end;}; + {pred = function(s) return s:match '^(>+)([^%s]*)%s*(.*)$' end, + fn = function(l,c,j,d) + local lvl,id,txt = l:match '^(>+)([^%s]*)%s*(.*)$' + lvl = utf8.len(lvl) + local last = d[#d] + local node + local ctx + if last and last.kind == 'quote' and (id == nil or id == '' or id == last.id) then + node = last + ctx = node.ctx + ctx.line = c.line -- is this enough?? + else + local doc + doc, ctx = c.doc:sub(c) + node = { kind = 'quote', doc = doc, ctx = ctx, id = id } + j:hook('block_insert', c, node, l) + table.insert(d, node) + end + + ct.parse_line(txt, ctx, ctx.sec.blocks) + end}; {seq = '~~~', fn = blockwrap(function(l,c,j) local extract = function(ptn, str) local start, stop = str:find(ptn) if not start then return nil, str end @@ -1170,9 +1234,10 @@ end table.insert(ctx.mode.listing.lines, newline) job:hook('block_listing_newline',ctx,ctx.mode.listing,newline) end - else + elseif ctx.mode.kind == 'quote' then + else local mf = job:proc('modes', ctx.mode.kind) if not mf then ctx:fail('unimplemented syntax mode %s', ctx.mode.kind) end @@ -1193,9 +1258,9 @@ if not tryseqs(ct.ctlseqs) then local found = false - for eb, ext, state in job:each('blocks') do + for eb, ext, state in job:each 'blocks' do if tryseqs(eb, state) then found = true break end end if not found then @@ -1217,13 +1282,9 @@ -- this object is threaded down through the parse tree -- and copied to store information like the origin of the -- element in the source code local ctx = ct.ctx.mk(src) - ctx.line = 0 - ctx.doc = ct.doc.mk() - ctx.doc.src = src - ctx.sec = ctx.doc:mksec() -- toplevel section - ctx.sec.origin = ctx:clone() + ctx:init(ct.doc.mk(), src) ctx.lang = mode['meta:lang'] if mode['parse:enc'] then local e = ss.str.enc[mode['parse:enc']] if not e then