cortav  Diff

Differences From Artifact [c52f4282f5]:

To Artifact [18c311a386]:


   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