129
130
131
132
133
134
135
136
137
138
139
140
141
142
...
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
...
264
265
266
267
268
269
270
271
272
273
274
275
276
277
...
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
....
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
....
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
....
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
....
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
....
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
....
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
|
local rid = self.sec.refs[id]
if self.sec.refs[id] then
return self.sec.refs[id], id, self.sec
else self:fail("no such ref %s in current section", id or '') end
else
local sec, ref = string.match(id, "(.-)%.(.+)")
local s = self.doc.sections[sec]
if s then
if s.refs[ref] then
return s.refs[ref], ref, sec
else self:fail("no such ref %s in section %s", ref, sec) end
else self:fail("no such section %s", sec) end
end
end
................................................................................
blocks = {};
refs = {};
depth = 0;
kind = 'ordinary';
} end;
construct = function(self, id, depth)
self.id = id
self.depth = depth
end;
fns = {
visible = function(self)
if self.kind == 'nonprinting' then return false end
local invisibles = {
['break'] = true;
reference = true;
................................................................................
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 {
sections = {};
secorder = {};
embed = {};
................................................................................
return spans
end
local function
blockwrap(fn)
return function(l,c,j,d)
local block = fn(l,c,j,d)
block.origin = c:clone();
table.insert(d, block);
j:hook('block_insert', c, block, l)
if block.spans then
c.doc.docjob:hook('meddle_span', block.spans, block)
end
end
end
local insert_paragraph = blockwrap(function(l,c)
if l:sub(1,1) == '.' then l = l:sub(2) end
return {
................................................................................
end)};
{seq = '\t\t', fn = function(l,c,j,d)
local last = d[#d]
if (not last) or (last.kind ~= 'reference') then
c:fail('reference continuations must immediately follow a reference')
end
local str = l:match '^\t\t(.-)%s*$'
last.val = last.val .. '\n' .. str
c.sec.refs[last.key] = last.val
end};
{seq = '\t', pred = function(l)
return (l:match '\t+([^:]+):%s*(.*)$')
end; fn = blockwrap(function(l,c,j,d)
local ref, val = l:match '\t+([^:]+):%s*(.*)$'
local last = d[#d]
local rsrc
if last and last.kind == 'resource' then
last.props[ref] = val
rsrc = last
elseif last and last.kind == 'reference' and last.rsrc then
last.rsrc.props[ref] = val
rsrc = last.rsrc
else
c.sec.refs[ref] = val
end
................................................................................
c:fail('extension %s does not support critical directive %s', cmd, topcmd)
end
end
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)
................................................................................
end
j:hook('mode_switch', c, mode)
c.mode = mode
if id then
if c.sec.refs[id] then c:fail('duplicate ID %s', id) end
c.sec.refs[id] = c.mode.listing
end
j:hook('block_insert', c, mode.listing, l)
return c.mode.listing;
end)};
{pred = function(s,c)
if s:match '^[%-_][*_%-%s]+' then return true end
if startswith(s, '—') then
for c, p in ss.str.each(c.doc.enc,s) do
if ({
................................................................................
})[c] ~= true then return false end
end
return true
end
end; fn = blockwrap(function()
return { kind = 'horiz-rule' }
end)};
{seq='@', fn=blockwrap(function(s,c)
local id = s:match '^@%s*(.-)%s*$'
local rsrc = {
kind = 'resource';
props = {};
id = id;
}
if c.sec.refs[id] then
c:fail('an object with id “%s” already exists in that section',id)
else
c.sec.refs[id] = rsrc
end
return rsrc
end)};
{fn = insert_paragraph};
}
function ct.parse_line(l, ctx, dest)
local newspan
local job = ctx.doc.stage.job
job:hook('line_read',ctx,l)
if l then
l = l:gsub("^ +","") -- trim leading spaces
end
if ctx.mode then
if ctx.mode.kind == 'code' then
if l and l:match '^~~~%s*$' then
job:hook('block_listing_end',ctx,ctx.mode.listing)
job:hook('mode_switch', c, nil)
ctx.mode = nil
................................................................................
if ctx.mode.expand
then newline = ct.parse_span(l, ctx)
else newline = {l}
end
table.insert(ctx.mode.listing.lines, newline)
job:hook('block_listing_newline',ctx,ctx.mode.listing,newline)
end
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
mf(job, ctx, l, dest) --NOTE: you are responsible for triggering the appropriate hooks if you insert anything!
end
................................................................................
local function
is_whitespace(cp)
return ctx.doc.enc.iswhitespace(cp)
end
if setup then setup(ctx) end
for full_line in file:lines() do ctx.line = ctx.line + 1
local l
for p, c in utf8.codes(full_line) do
if not is_whitespace(c) then
l = full_line:sub(p)
break
end
end
ct.parse_line(l, ctx, ctx.sec.blocks)
end
for i, sec in ipairs(ctx.doc.secorder) do
for refid, r in ipairs(sec.refs) do
if type(r) == 'table' and r.kind == 'resource' and r.props.src then
local lines = ss.str.breaklines(ctx.doc.enc, r.props.src)
local srcs = {}
for i,l in ipairs(lines) do
local args = ss.str.breakwords(ctx.doc.enc, l, 2, {escape=true})
if #args < 3 then
r.origin:fail('invalid syntax for resource %s', t.ref)
end
local mime = ss.mime(args[2]);
local class = mimeclasses[mime]
table.insert(srcs, {
mode = args[1];
mime = mime;
uri = args[3];
class = class or mime[1];
})
end
--ideally move this into its own mimetype lib
r.srcs = srcs
-- note that resources do not themselves have kinds. when a
-- document requests to insert a resource, the renderer must
-- iterate through the sources and find the first source it
-- is capable of emitting. this allows constructions like
-- emitting a video for HTML outputs, a photo for printers,
-- and a screenplay for tty/plaintext outputs.
end
end
end
ctx.doc.stage = nil
ctx.doc.docjob:hook('meddle_ast')
return ctx.doc
end
function ct.expand_var(v)
local val
|
>
>
>
>
>
>
>
>
|
>
>
|
|
|
|
|
>
>
>
>
|
>
>
|
|
|
|
|
<
>
<
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
|
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
|
>
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<
|
>
>
>
|
>
|
<
<
|
<
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
...
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
...
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
...
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
....
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
....
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
....
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
....
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
....
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
....
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
|
local rid = self.sec.refs[id]
if self.sec.refs[id] then
return self.sec.refs[id], id, self.sec
else self:fail("no such ref %s in current section", id or '') end
else
local sec, ref = string.match(id, "(.-)%.(.+)")
local s = self.doc.sections[sec]
if not s then -- fall back on inheritance tree
for i, p in ipairs(self.doc.parents) do
if p.sections[sec] then
s = p.sections[sec]
break
end
end
end
if s then
if s.refs[ref] then
return s.refs[ref], ref, sec
else self:fail("no such ref %s in section %s", ref, sec) end
else self:fail("no such section %s", sec) end
end
end
................................................................................
blocks = {};
refs = {};
depth = 0;
kind = 'ordinary';
} end;
construct = function(self, id, depth)
self.id = id
self.depth = depth or self.depth
end;
fns = {
visible = function(self)
if self.kind == 'nonprinting' then return false end
local invisibles = {
['break'] = true;
reference = true;
................................................................................
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
nctx.docDepth = (ctx.docDepth or 0) + ctx.sec.depth - 1
return newdoc, nctx
end;
};
mk = function(...) return {
sections = {};
secorder = {};
embed = {};
................................................................................
return spans
end
local function
blockwrap(fn)
return function(l,c,j,d)
local block = fn(l,c,j,d)
if block then
block.origin = c:clone();
table.insert(d, block);
j:hook('block_insert', c, block, l)
if block.spans then
c.doc.docjob:hook('meddle_span', block.spans, block)
end
end
end
end
local insert_paragraph = blockwrap(function(l,c)
if l:sub(1,1) == '.' then l = l:sub(2) end
return {
................................................................................
end)};
{seq = '\t\t', fn = function(l,c,j,d)
local last = d[#d]
if (not last) or (last.kind ~= 'reference') then
c:fail('reference continuations must immediately follow a reference')
end
local str = l:match '^\t\t(.-)%s*$'
if last.val == '' then
last.val = str
else
last.val = last.val .. '\n' .. str
end
c.sec.refs[last.key] = last.val
end};
{seq = '\t', pred = function(l)
return (l:match '\t+([^:]+):%s*(.*)$')
end; fn = blockwrap(function(l,c,j,d)
local ref, val = l:match '\t+([^:]+):%s*(.*)$'
local last = d[#d]
local rsrc
if last and last.kind == 'resource' then
last.props[ref] = val
j:hook('rsrc_set_prop', c, last, ref, val, l)
rsrc = last
elseif last and last.kind == 'reference' and last.rsrc then
last.rsrc.props[ref] = val
rsrc = last.rsrc
else
c.sec.refs[ref] = val
end
................................................................................
c:fail('extension %s does not support critical directive %s', cmd, topcmd)
end
end
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 id,txt = l:match '^>([^>%s]*)%s*(.*)$'
if id == '' then id = nil end
local last = d[#d]
local node
local ctx
if last and last.kind == 'quote' and (id == nil 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, origin = c }
table.insert(d, node)
j:hook('block_insert', c, node, l)
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)
................................................................................
end
j:hook('mode_switch', c, mode)
c.mode = mode
if id then
if c.sec.refs[id] then c:fail('duplicate ID %s', id) end
c.sec.refs[id] = c.mode.listing
end
return c.mode.listing;
end)};
{pred = function(s,c)
if s:match '^[%-_][*_%-%s]+' then return true end
if startswith(s, '—') then
for c, p in ss.str.each(c.doc.enc,s) do
if ({
................................................................................
})[c] ~= true then return false end
end
return true
end
end; fn = blockwrap(function()
return { kind = 'horiz-rule' }
end)};
{seq='@', fn=function(s,c,j,d)
local function mirror(b)
local ch = {}
local rev = {
['['] = ']'; [']'] = '[';
['{'] = '}'; ['}'] = '{';
['('] = ')'; [')'] = '(';
['<'] = '>'; ['>'] = '<';
}
for i = 1,#b do
local c = string.sub(b,-i,-i)
if rev[c] then
ch[i] = rev[c]
else
ch[i] = c
end
end
return table.concat(ch)
end
local id,rest = s:match '^@([^%s]*)%s*(.*)$'
local bs, brak = rest:match '()([{[(<][^%s]*)%s*$'
local src
if brak then
src = rest:sub(1,bs-1):gsub('%s+$','')
else src = rest end
if src == '' then src = nil end
if id == '' then id = nil end
local rsrc = {
kind = 'resource';
props = {src = src};
id = id;
origin = c;
}
if brak then
rsrc.bracket = {
open = brak;
close = mirror(brak);
}
rsrc.raw = '';
if src == nil then
rsrc.props.src = 'text/x.cortav'
end
else
-- load the raw body, where possible
end
if id then
if c.sec.refs[id] then
c:fail('an object with id “%s” already exists in that section',id)
else
c.sec.refs[id] = rsrc
end
end
table.insert(d, rsrc)
j:hook('block_insert', c, rsrc, s)
if id == '' then --shorthand syntax
local embed = {
kind = 'embed';
rsrc = rsrc;
origin = c;
}
table.insert(d, embed)
j:hook('block_insert', c, embed, s)
end
if brak then
c.mode = {
kind = 'inline-rsrc';
rsrc = rsrc;
indent = nil;
depth = 0;
}
end
end};
{seq='&$', fn=blockwrap(function(s,c)
local id, args = s:match('^&$([^%s]+)%s?(.-)$')
if id == nil or id == '' then
c:fail 'malformed macro block'
end
local argv = ss.str.split(c.doc.enc, args, c.doc.enc.encodeUCS'|', {esc=true})
return {
kind = 'macro';
macro = id;
args = argv;
}
end)};
{seq='&', fn=blockwrap(function(s,c)
local id, cap = s:match('^&([^%s]+)%s*(.-)%s*$')
if id == nil or id == '' then
c:fail 'malformed embed block'
end
if cap == '' then cap = nil end
return {
kind = 'embed';
ref = id;
cap = cap;
}
end)};
{fn = insert_paragraph};
}
function ct.parse_line(rawline, ctx, dest)
local newspan
local job = ctx.doc.stage.job
job:hook('line_read',ctx,rawline)
local l
if rawline then
l = rawline:gsub("^ +","") -- trim leading spaces
end
if ctx.mode then
if ctx.mode.kind == 'code' then
if l and l:match '^~~~%s*$' then
job:hook('block_listing_end',ctx,ctx.mode.listing)
job:hook('mode_switch', c, nil)
ctx.mode = nil
................................................................................
if ctx.mode.expand
then newline = ct.parse_span(l, ctx)
else newline = {l}
end
table.insert(ctx.mode.listing.lines, newline)
job:hook('block_listing_newline',ctx,ctx.mode.listing,newline)
end
elseif ctx.mode.kind == 'inline-rsrc' then
local r = ctx.mode.rsrc
if rawline then
if rawline == r.bracket.close then
if ctx.mode.depth == 0 then
-- TODO how to handle depth?
ctx.mode = nil
end
else
if r.indent ~= nil then
r.raw = r.raw .. '\n'
else
r.indent = (rawline:sub(1,1) == '\t')
end
if r.indent == true then
if rawline:sub(1,1) == '\t' then
rawline = rawline:sub(2)
end
end
r.raw = r.raw .. rawline
end
end
else
local mf = job:proc('modes', ctx.mode.kind)
if not mf then
ctx:fail('unimplemented syntax mode %s', ctx.mode.kind)
end
mf(job, ctx, l, dest) --NOTE: you are responsible for triggering the appropriate hooks if you insert anything!
end
................................................................................
local function
is_whitespace(cp)
return ctx.doc.enc.iswhitespace(cp)
end
if setup then setup(ctx) end
for full_line in file:lines() do ctx.line = ctx.line + 1
-- local l
-- for p, c in utf8.codes(full_line) do
-- if not is_whitespace(c) then
-- l = full_line:sub(p)
-- break
-- end
-- end
ct.parse_line(full_line, ctx, ctx.sec.blocks)
end
for i, sec in ipairs(ctx.doc.secorder) do
for n, r in pairs(sec.blocks) do
if r.kind == 'resource' and r.props.src then
local lines = ss.str.breaklines(ctx.doc.enc, r.props.src)
local srcs = {}
for i,l in ipairs(lines) do
local args = ss.str.breakwords(ctx.doc.enc, l, 2, {escape=true})
if #args > 3 or (r.raw and #args > 2) then
r.origin:fail('invalid syntax for resource %s', r.id or '(anonymous)')
end
local p_mode, p_mime, p_uri
if r.raw then
p_mode = 'embed'
end
if #args == 1 then
if r.raw then -- inline content
p_mime = ss.mime(args[1])
else
p_uri = args[1]
end
elseif #args == 2 then
local ok, m = pcall(ss.mime, args[1])
if r.raw then
if not ok then
r.origin:fail('invalid mime-type “%s”', args[1])
end
p_mode, p_mime = args[1], m
else
if ok then
p_mime, p_uri = m, args[2]
else
p_mode, p_uri = table.unpack(args)
end
end
else
p_mode, p_mime, p_uri = table.unpack(args)
p_mime = ss.mime(args[2])
end
local resource = {
mode = p_mode;
mime = p_mime or 'text/x.cortav';
uri = p_uri and ss.uri(p_uri) or nil;
}
if resource.mode == 'embed' or resource.mode == 'auto' then
-- the resource must be available for reading within this job
-- open it and read its source into memory
if resource.uri then
if resource.uri:canfetch() then
resource.raw = resource.uri:fetch()
elseif resource.mode == 'auto' then
-- resource cannot be accessed; force linking
resource.mode = 'link'
else
r.origin:fail('resource “%s” wants to embed unfetchable URI “%s”',
r.id or "(anonymous)", tostring(resource.uri))
end
elseif r.raw then
resource.raw = r.raw
else
r.origin:fail('resource “%s” is not inline and supplies no URI',
r.id or "(anonymous)")
end
-- the resource has been cached. check the mime-type to see if
-- we need to parse it or if it is suitable as-is
if resource.mime.class == "text" then
if resource.mime.kind == "x.cortav" then
local sd, sc = r.origin.doc:sub(r.origin)
local lines = ss.str.breaklines(r.origin.doc.enc, resource.raw, {})
for i, ln in ipairs(lines) do
sc.line = sc.line + 1
ct.parse_line(ln, sc, sc.sec.blocks)
end
resource.doc = sd
end
end
end
table.insert(srcs, resource)
end
r.srcs = srcs
-- note that resources do not themselves have kinds. when a
-- document requests to insert a resource, the renderer must
-- iterate through the sources and find the first source it
-- is capable of emitting. this allows constructions like
-- emitting a video for HTML outputs, a photo for printers,
-- and a screenplay for tty/plaintext outputs.
end
end
end
-- expand block macros
for i, sec in ipairs(ctx.doc.secorder) do
for n, r in pairs(sec.blocks) do
if r.kind == 'macro' then
local mc = r.origin:clone()
mc.invocation = r
local mac = r.origin:ref(r.macro)
if not mac then
r.origin:fail('no such reference or resource “%s”', r.macro)
end
local subdoc, subctx = ctx.doc:sub(mc)
local rawbody
if type(mac) == 'string' then
rawbody = mac
elseif mac.raw then
rawbody = mac.raw
else
r.origin:fail('block macro “%s” must be either a reference or an embedded text/x.cortav resource', r.macro)
end
local lines = ss.str.breaklines(ctx.doc.enc, rawbody)
for i, ln in ipairs(lines) do
ct.parse_line(ln, subctx, subctx.sec.blocks)
end
r.doc = subdoc
end
end
end
ctx.doc.stage = nil
ctx.doc.docjob:hook('meddle_ast')
return ctx.doc
end
function ct.expand_var(v)
local val
|