Skip to content

Commit a2883b7

Browse files
committed
Implement footnote lexing
1 parent afadc9f commit a2883b7

File tree

3 files changed

+73
-19
lines changed

3 files changed

+73
-19
lines changed

README.org

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ TODO
6767
| OrgEntity | | X | | | | |
6868
| LaTeX Fragment | | X | | | | |
6969
| ExportSnippet | | X | | | | |
70-
| FootnoteReference | | | | | | |
70+
| FootnoteReference | | X | | | | |
7171
| InlineBabelCall | | | | | | |
7272
| InlineSrcBlock | | | | | | |
7373
| RadioLink | | | | | | |

src/lexer.jl

Lines changed: 59 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,28 @@ introducing type instability.
6868
"""
6969
const NONE_TOKEN = Token(K"", 0, 0), UInt32(0)
7070

71+
"""
72+
@sometoken(args...)
73+
74+
Return the first of `args` that is not `NONE_TOKEN`, finally returning
75+
`NONE_TOKEN` only if all arguments are `NONE_TOKEN`.
76+
"""
77+
macro sometoken(args...)
78+
expr = :(NONE_TOKEN)
79+
for arg in reverse(args)
80+
val = gensym("tok")
81+
expr = quote
82+
$val = $(esc(arg))
83+
if $val != NONE_TOKEN
84+
$val
85+
else
86+
$expr
87+
end
88+
end
89+
end
90+
expr
91+
end
92+
7193
function lexnext(state::LexerState, bytes::DenseVector{UInt8}, start::UInt32)
7294
linestart, newlines = @inline skipnewlines(bytes, start)
7395
if start == 1 && state.lastelement != K"<paragraph"
@@ -141,10 +163,19 @@ function lexnext_element(state::LexerState, bytes::DenseVector{UInt8},
141163
end
142164
elseif chr == UInt8('[') && pos == linestart
143165
fndef = lex_footnotedef(state, bytes, pos)
144-
if fndef != NONE_TOKEN && K"footnote_definition" state.ctx
145-
Token(K">footnote_definition", start - 0x1, start - 0x1), start
166+
if fndef != NONE_TOKEN
167+
if K"footnote_definition" state.ctx
168+
Token(K">footnote_definition", start - 0x1, start - 0x1), start
169+
else
170+
fndef
171+
end
146172
else
147-
fndef
173+
fnref = lex_footnoteref(state, bytes, pos)
174+
if fnref != NONE_TOKEN
175+
Token(K"<paragraph", pos, pos), pos
176+
else
177+
NONE_TOKEN
178+
end
148179
end
149180
elseif chr == UInt8('|') && K"table" state.restriction
150181
Token(K"<table", pos, pos), pos
@@ -199,13 +230,12 @@ function lexnext_object(state::LexerState, bytes::DenseVector{UInt8},
199230
elseif chr (UInt8('*'), UInt8('/'), UInt8('_'), UInt8('='), UInt8('~'), UInt8('+'))
200231
lex_markup(state, bytes, pos)
201232
elseif chr == UInt8('\\')
202-
tok = lex_entity(state, bytes, pos)
203-
if tok == NONE_TOKEN
204-
tok = lex_latexfrag(state, bytes, pos)
205-
end
206-
tok
233+
@sometoken(lex_entity(state, bytes, pos),
234+
lex_latexfrag(state, bytes, pos))
207235
elseif chr == UInt8('@')
208236
lex_exportsnippet(state, bytes, pos)
237+
elseif chr == UInt8('[') && hasprefix(bytes, pos + 0x1, "fn:")
238+
lex_footnoteref(state, bytes, pos)
209239
else
210240
NONE_TOKEN
211241
end
@@ -286,15 +316,14 @@ function lex_item(state::LexerState, bytes::DenseVector{UInt8}, linestart::UInt3
286316
end
287317

288318
function lex_hashplus(state::LexerState, bytes::DenseVector{UInt8}, pos::UInt32)
289-
blk = lex_block(state, bytes, pos)
290-
blk != NONE_TOKEN && return blk
291-
blk = lex_dynamicblock(state, bytes, pos)
292-
blk != NONE_TOKEN && return blk
293-
if K"keyword" in state.restriction
294-
blk = lex_keyword(state, bytes, pos)
295-
blk != NONE_TOKEN && return blk
296-
end
297-
NONE_TOKEN
319+
@sometoken(
320+
lex_block(state, bytes, pos),
321+
lex_dynamicblock(state, bytes, pos),
322+
if K"keyword" state.restriction
323+
lex_keyword(state, bytes, pos)
324+
else
325+
NONE_TOKEN
326+
end)
298327
end
299328

300329
function lex_block((; ctx)::LexerState, bytes::DenseVector{UInt8}, start::UInt32)
@@ -606,7 +635,19 @@ function lex_exportsnippet(::LexerState, bytes::DenseVector{UInt8}, pos::UInt32)
606635
Token(settag(K"export_snippet", langtag), pos, closepos), closepos + 0x1
607636
end
608637

609-
# TODO: Footnote references
638+
function lex_footnoteref(::LexerState, bytes::DenseVector{UInt8}, pos::UInt32)
639+
refend = skipbalanced(bytes, pos, '[' => ']')
640+
iszero(refend) && return NONE_TOKEN
641+
nameend = skipwords(bytes, pos + ncodeunits("[fn:") % UInt32, ('-', '_'))
642+
fnkind = if bytes[nameend] == UInt8(']')
643+
0x01 # label
644+
elseif bytes[nameend] == UInt8(':')
645+
0x02 + UInt8(nameend > pos + ncodeunits("[fn:") % UInt32)
646+
else
647+
return NONE_TOKEN
648+
end
649+
Token(settag(K"footnote_reference", fnkind), pos, refend - 0x1), refend
650+
end
610651

611652
# TODO: Citations
612653

test/runtests.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,19 @@ end
499499
Token(K"export_snippet[148]", 1, 6),
500500
Token(K"export_snippet[178]", 7, 12)]
501501
end
502+
@testset "Footnote references" begin
503+
@test collect(Lexer(" [fn:1]")) ==
504+
[Token(K"<paragraph", 1, 1)
505+
Token(K"footnote_reference[1]", 2, 7)]
506+
@test collect(Lexer("[fn::desc]")) ==
507+
[Token(K"<paragraph", 1, 1)
508+
Token(K"footnote_reference[2]", 1, 10)]
509+
@test collect(Lexer("[fn:label:desc]")) ==
510+
[Token(K"<paragraph", 1, 1)
511+
Token(K"footnote_reference[3]", 1, 15)]
512+
@test collect(Lexer("[fn:in valid]")) ==
513+
[Token(K"<paragraph", 1, 1)]
514+
end
502515
@testset "Type inference" begin
503516
@testset "Utilities" begin
504517
bytes, pos = codeunits("abc"), UInt32(1)

0 commit comments

Comments
 (0)